我没有心事往事只是只蚂蚁,生下来胳膊大腿就是一样细

gcc指定-fPIC编译的时候内嵌汇编需要注意的问题

上一篇 / 下一篇  2004-10-08 00:00:00 / 个人分类:tech

放假前遇到一个bug,放假的时候,简单分析了一下


【问题】

程序中需要读取cpuid,我用下面的代码来判定cpu的id是否存在而且可读,显然这里内嵌汇编是必然的。(由于这个blog无法输出反斜线,所以下面的代码中去掉了换行符号,直接copy是不能执行的)

    // if we can change the value of bit21 in EFLAGS register,
// the CPUID instruction is executable
asm(" pushfl"
" pop %%eax" // get EFLAGS into eax
" movl %%eax, %%ebx" // keep a copy
" xor $0x200000, %%eax" // toggle CPUID bit
" push %%eax"
" popfl" // set new EFLAGS
" pushfl"
" pop %%eax" // EFLAGS back into eax
" xor %%ebx, %%eax" // have we changed the ID bit?
" je 1f"
" movl $1, %%eax"
" cpuid" // get processor features
" test $0x40000, %%edx" // check the serial number bit
" jz 1f"
" movl $1, %0"
" jmp 2f"
"1: movl $0, %0"
"2: popa"
:"=m"(sn_present)::"eax", "ebx", "ecx", "edx");
return sn_present;

这段代码在一个standalone的应用程序中是可以很好工作的,但是编译成动态库,通过jni调用的时候,却引发了异常,出现segmentation fault了。

【分析】


jni调用C++,要求用C++编写的代码是以动态链接库的形式给出的。所以编译这个库的时候我们需要指定-fPIC,也就是指定生成的代码是位置无关的(position independent code)。也许这就是问题所在。

在-fPIC情况下,编译器将把所有的绝对位移用相对于0地址的相对位移代替,然后在引用这些位移的时候用库的装载地址作为基址来寻址。

普通的可执行文件的跳转表,如下所示:

clkrst@ips ~/ipsdev/core/src/license $ objdump -j .plt -d computerid

computerid: file format elf32-i386

Disassembly of section .plt:

080489e8 <.plt>:
80489e8: ff 35 78 c2 04 08 pushl 0x804c278
80489ee: ff 25 7c c2 04 08 jmp *0x804c27c
80489f4: 00 00 add %al,(%eax)
80489f6: 00 00 add %al,(%eax)
80489f8: ff 25 80 c2 04 08 jmp *0x804c280
80489fe: 68 00 00 00 00 push $0x0
8048a03: e9 e0 ff ff ff jmp 80489e8 <_init+0x18>
8048a08: ff 25 84 c2 04 08 jmp *0x804c284 ----> [*]
8048a0e: 68 08 00 00 00 push $0x8
8048a13: e9 d0 ff ff ff jmp 80489e8 <_init+0x18>
8048a18: ff 25 88 c2 04 08 jmp *0x804c288 ----> [*]
8048a1e: 68 10 00 00 00 push $0x10
8048a23: e9 c0 ff ff ff jmp 80489e8 <_init+0x18>
8048a28: ff 25 8c c2 04 08 jmp *0x804c28c ----> [*]
..............

标记*的地方就是跳转代码,可以看到这些地址是绝对地址。

再来看一下用-fPIC生成的位置无关代码的跳转表是什么样的

clkrst@ips ~/ipsdev/core/jni $ objdump -j .plt -d libtestjava.so

libtestjava.so: file format elf32-i386

Disassembly of section .plt:

000039d8 <.plt>:
39d8: ff b3 04 00 00 00 pushl 0x4(%ebx)
39de: ff a3 08 00 00 00 jmp *0x8(%ebx)
39e4: 00 00 add %al,(%eax)
39e6: 00 00 add %al,(%eax)
39e8: ff a3 0c 00 00 00 jmp *0xc(%ebx)
39ee: 68 00 00 00 00 push $0x0
39f3: e9 e0 ff ff ff jmp 39d8 <_init+0x18>
39f8: ff a3 10 00 00 00 jmp *0x10(%ebx) ----> [*]
39fe: 68 08 00 00 00 push $0x8
3a03: e9 d0 ff ff ff jmp 39d8 <_init+0x18>
3a08: ff a3 14 00 00 00 jmp *0x14(%ebx) ----> [*]
3a0e: 68 10 00 00 00 push $0x10
3a13: e9 c0 ff ff ff jmp 39d8 <_init+0x18>
3a18: ff a3 18 00 00 00 jmp *0x18(%ebx) ----> [*]
..............

显然跳转的地址是用ebx作为基址的相对地址,注意这里使用的基址寄存器是ebx。ebx是在代码开始执行之前,根据加载地址设置好的。

这样我们再回过头来,分析我们的代码

" pop %%eax" // get EFLAGS into eax
" movl %%eax, %%ebx" // keep a copy
" xor $0x200000, %%eax" // toggle CPUID bit

显然在代码中我们破坏了ebx的值,而且没有恢复,那么后来的调用中,自然就导致了segmentation fault。
虽然我们在汇编代码的约束部里,指定了ebx是可能会被破坏的,但是gcc没有替我们生成相应的保存和恢复代码。

随之我在代码的前后加上了push %ebx和pop %ebx,就一切正常了。


【结论】
gcc在生成位置无关代码的时候,内部使用了ebx作为基址寄存器。如果不使用内嵌汇编,那么gcc自然会帮助你维持ebx的值始终有效。但是如果使用了内嵌汇编,gcc常常就有点力不从心了,所以这时候,一定要自己留意保存好ebx的值。

btw,gcc的内嵌汇编语法复杂很多,我想一定有办法可以通过指定约束来达到不破坏ebx的目的,但是显然顺手加上句push/pop更为简单了。呵呵。


TAG:

引用 删除 天天溢出   /   2008-05-13 17:34:39
很清楚,受益匪浅。
 

评分:0

我来说两句

显示全部

:loveliness: :handshake :victory: :funk: :time: :kiss: :call: :hug: :lol :'( :Q :L ;P :$ :P :o :@ :D :( :)

Open Toolbar