Pergunta

I'm using getopt_long in a program (source here), and, when testing its behaviour when given a invalid long option (--stack-overflow), and I get:

[marcoms@baguette16 btcwatch]$ ./btcwatch --stack-overflow
Segmentation fault (core dumped)

For context, its in the getopt_long() loop, i.e.:

while((opt = getopt_long(argc,
                         argv,
                         OPTSTRING,
                         long_options,
                         &longopt_i)) != -1)
{

    ...

    default:
        exit(EXIT_FAILURE);
        break;

Instead of:

error(EXIT_FAILURE, 0, "unknown option !?");

(The exit() code SIGSEGVs)

The crazy thing is, default should not be (and is not, according to gdb), executed.

gdb shows that it crashes immediately on the getopt_long() call,

(gdb) start --stack-overflow    
Temporary breakpoint 1, main (argc=2, argv=0x7fffffffe598) at src/main.c:96
96      btcdbg("main()");
(gdb) s
btcdbg (fmt=0x403260 "main()") at src/btcutil.c:50
50  }
(gdb) 
main (argc=2, argv=0x7fffffffe598) at src/main.c:118
118     const struct option long_options[] = {
(gdb) 
211     api_err.err = false;
(gdb) 
212     colour = false;
(gdb) 
213     found_path = false;
(gdb) 
214     fp = NULL;
(gdb) 
215     n = 1.0;
(gdb) 
216     newlp = NULL;
(gdb) 
217     pn = argv[0];
(gdb) 
218     reverse = false;
(gdb) 
219     verbose = false;
(gdb) 
221     strcpy(currcy, "USD");
(gdb) 
223     setlocale(LC_ALL, "");  // sets the locale to the system's default
(gdb) 
225     while((opt = getopt_long(
(gdb) 

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7643e2c in __strncmp_sse2 () from /usr/lib/libc.so.6

But when it is run with error() instead of exit(), it proceeds normally:

(gdb) start --stack-overflow
Temporary breakpoint 1, main (argc=2, argv=0x7fffffffe598) at src/main.c:96
96      btcdbg("main()");
(gdb) s
btcdbg (fmt=0x4032a0 "main()") at src/btcutil.c:50
50  }
(gdb) 
main (argc=2, argv=0x7fffffffe598) at src/main.c:118
118     const struct option long_options[] = {
(gdb) 
211     api_err.err = false;
(gdb) 
212     colour = false;
(gdb) 
213     found_path = false;
(gdb) 
214     fp = NULL;
(gdb) 
215     n = 1.0;
(gdb) 
216     newlp = NULL;
(gdb) 
217     pn = argv[0];
(gdb) 
218     reverse = false;
(gdb) 
219     verbose = false;
(gdb) 
221     strcpy(currcy, "USD");
(gdb) 
223     setlocale(LC_ALL, "");  // sets the locale to the system's default
(gdb) 
225     while((opt = getopt_long(
(gdb) 
/home/marcoms/code/btcwatch/./btcwatch: unrecognized option '--stack-overflow'
232         btcdbg("got option '%c'", opt);
(gdb) 
btcdbg (fmt=0x4032a8 "got option '%c'") at src/btcutil.c:50
50  }
(gdb) 
main (argc=2, argv=0x7fffffffe598) at src/main.c:233
233         switch(opt) {
(gdb) 
236                 help(pn, optarg);
(gdb) 
help (prog_nm=0x7fffffffe8d8 "/home/marcoms/code/btcwatch/./btcwatch", topic=0x0) at src/btcutil.c:69
69      btcdbg("help()");
(gdb) 
btcdbg (fmt=0x403bfc "help()") at src/btcutil.c:50
50  }
(gdb) 
help (prog_nm=0x7fffffffe8d8 "/home/marcoms/code/btcwatch/./btcwatch", topic=0x0) at src/btcutil.c:71
71      char currcies[][3 + 1] = {
(gdb) 
101     char topics[][16] = {
(gdb) 
117     if(!topic) {
(gdb) 
118         bputs("Usage: "); bputs(prog_nm); bputs(" [OPTION]\n");
(gdb) 
Usage: /home/marcoms/code/btcwatch/./btcwatch [OPTION]
119         bputs(
(gdb) 
Get and monitor Bitcoin trade information

Options:       Long options:
  -C             --compare              comare current price with stored price
  -S             --store                store current price
  -a             --all                  equivalent to -pbs
  -b             --buy                  print buy price
  -c CURRENCY    --currency=CURRENCY    set conversion currency
  -n AMOUNT      --amount=AMOUNT        set the amount to convert
  -o             --colour, --color      enable use of colour
  -p             --ping                 check for a successful JSON response
  -r             --reverse              convert currency to Bitcoin
  -s             --sell                 print sell price
  -v             --verbose              increase verbosity

  -h [topic]     --help[=topic]         print this help, or help designated by topic
                                        use --help=topics for available topics
  -V             --version              print version number

Report bugs to marco@scannadinari.co.uk
btcwatch home page: <https://github.com/marcoms/btcwatch>
142         exit(EXIT_SUCCESS);
(gdb) 
[Inferior 1 (process 25752) exited normally]

At this point, I really am stumped. What could be causing the segfault? Of course, I could just use error() instead of exit, but that's incredibly unsatisfying.

Foi útil?

Solução

The problem is that your options array is not properly terminated:

    const struct option long_options[] = {
            // ...
            // This is the last element
            {
                    .name = "verbose",
                    .has_arg = no_argument,
                    .flag = NULL,
                    .val = 'v'
            }
    };

getopt_long(3) requires that the options array be terminated with all zeros so that it knows how big the array is -- notice that you never actually pass the array size into getopt_long. So what's happening is it's walking past the end of your array, looking for the terminator, and then it starts reading memory out-of-bounds, which Valgrind rightly complains about. Undefined Behavior results.

The fix is simple:

const struct option long_options[] = {
            // ...
            {
                    .name = "verbose",
                    .has_arg = no_argument,
                    .flag = NULL,
                    .val = 'v'
            },

            // Array terminator
            {0, 0, 0, 0}
};
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top