読者です 読者をやめる 読者になる 読者になる

gdbでgetopt()のoptind/optoptを参照したときに値が違うように見える

結構ハマったのでメモ。

#include <stdio.h>
#include <getopt.h>

char *short_opts = "ab:";
struct option long_opts[] = {
	{"test", no_argument, 0, 0},
	{"add", required_argument, 0, 0},
	{0, 0, 0, 0}
}; 
int main(int argc, char *argv[]) {
	int c = 0;
	int longindex;

	while (1) {
		longindex = 0;
		c = getopt_long(argc, argv, short_opts, long_opts, &longindex);
		if (c == -1) {
			break;
		}

		fprintf(stdout, "%d(%x) %d(%x) %c(%x)\n",
				c, &c, optind, &optind, optopt, &optopt);
	}
	return 0;
}

というソースがあり、getoptのextern変数optind, optoptの実際の値とgdbで参照した値が違うのに気がついた。

$ cc -g getopt.c
$ ./a.out -a -b a --test --add A
97(4cc7b7ac) 2(600ae4) (600ae0)
98(4cc7b7ac) 4(600ae4) (600ae0)
0(4cc7b7ac) 5(600ae4) (600ae0)
0(4cc7b7ac) 7(600ae4) (600ae0)
$ gdb a.out
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/kohchi/C/a.out...done.
(gdb) b main
Breakpoint 1 at 0x4005e4: file getopt.c, line 14.
(gdb) run -a -b a --test --add A
Starting program: /home/kohchi/C/a.out -a -b a --test --add A

Breakpoint 1, main (argc=7, argv=0x7fffffffe1c8) at getopt.c:14
14		int c = 0;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6_5.2.x86_64
(gdb) n
18			longindex = 0;
(gdb) 
19			c = getopt_long(argc, argv, short_opts, long_opts, &longindex);
(gdb) 
20			if (c == -1) {
(gdb) 
24			fprintf(stdout, "%d(%x) %d(%x) %c(%x)\n",
(gdb) p c
$1 = 97
(gdb) p optind
$2 = 1
(gdb) p optopt
$3 = 63
(gdb) n
97(ffffe0cc) 2(600ae4) (600ae0)    ※あれ?optind=2 optopt=0になっている。
26		}
(gdb) 

で以下のページを参照したところ、シェアードライブラリの値と実行バイナリで使っているものが2つあるらしい。
コマンドを実行すると当然シェアードライブラリ中の値となるのだが、gdbで何も指定しないと実行バイナリのものを表示するらしい。

http://www.linuxquestions.org/questions/programming-9/gdb-does-not-print-right-value-908538/

なのでgdbでは以下のように"@@GLIBC_X.X.X"を指定するとシェアードライブラリの値を参照できるようだ。

$ gdb a.out
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/kohchi/C/a.out...done.
(gdb) b main
Breakpoint 1 at 0x4005e4: file getopt.c, line 14.
(gdb) run -a -b a --test --add A
Starting program: /home/kohchi/C/a.out -a -b a --test --add A

Breakpoint 1, main (argc=7, argv=0x7fffffffe1c8) at getopt.c:14
14		int c = 0;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6_5.2.x86_64
(gdb) n
18			longindex = 0;
(gdb) 
19			c = getopt_long(argc, argv, short_opts, long_opts, &longindex);
(gdb) 
20			if (c == -1) {
(gdb) p c
$1 = 97
(gdb) p /c c
$2 = 97 'a'
(gdb) n
24			fprintf(stdout, "%d(%x) %d(%x) %c(%x)\n",
(gdb) l
19			c = getopt_long(argc, argv, short_opts, long_opts, &longindex);
20			if (c == -1) {
21				break;
22			}
23	
24			fprintf(stdout, "%d(%x) %d(%x) %c(%x)\n",
25					c, &c, optind, &optind, optopt, &optopt);
26		}
27		return 0;
28	}
(gdb) n
97(ffffe0cc) 2(600ae4) (600ae0)
26		}
(gdb) p optind
$3 = 12になっていないぞ
(gdb) p &optind
$4 = (int *) 0x38bfd8e114   ※参照先も違う
(gdb) p optopt
$5 = 630になっていないぞ
(gdb) p &optopt
$6 = (int *) 0x38bfd8e11c   ※参照先も違う
(gdb) p optind[tabを2回押す]
optind               optind@@GLIBC_2.2.5  
(gdb) p 'optind@@GLIBC_2.2.5' 
$7 = 2                                            ※OK!
(gdb) p &'optind@@GLIBC_2.2.5' 
$8 = (<data variable, no debug info> *) 0x600ae4  ※OK!
(gdb) p optopt[tabを2回押す]
optopt               optopt@@GLIBC_2.2.5  
(gdb) p 'optopt@@GLIBC_2.2.5' 
$9 = 0                                            ※OK!
(gdb) p &'optopt@@GLIBC_2.2.5' 
$10 = (<data variable, no debug info> *) 0x600ae0 ※OK!
(gdb) info var optind
All variables matching regular expression "optind":

Non-debugging symbols:
0x0000000000600ae4  optind@@GLIBC_2.2.5
(gdb) info var optopt
All variables matching regular expression "optopt":

Non-debugging symbols:
0x0000000000600ae0  optopt@@GLIBC_2.2.5
(gdb) n
18			longindex = 0;
(gdb) quit
A debugging session is active.

	Inferior 1 [process 8020] will be killed.

Quit anyway? (y or n) y
$

optindやoptoptの値で分岐していたりして、gdbデバッグしたら確実にハマるよね。