对李先静文章中volatile变量的量化小分析

Sina WeiboBaiduLinkedInQQGoogle+RedditEvernote分享




下面是一段简单的代码,试图对李先静文章中的volatile进行一些量化分析。变量foo是一个static变量。下面分析了non volatile和volatile的不同的汇编语言结果。
static int foo;
void bar(void) {
foo = 0;
while (foo != 255)
;
}
.text
.align 4,0×90
.globl _bar
_bar:
LFB2:
pushq   %rbp
LCFI0:
movq    %rsp, %rbp
LCFI1:
movl    $0, _foo(%rip)//变量初始化为0
L2:
jmp     L2//死循环,因为编译优化,认为foo变量永远是0;
如果对变量做volatile处理,禁止编译优化。

static volatile int foo;
void bar(void) {
foo = 0;
while (foo != 255)
;
}

.text
.align 4,0×90
.globl _bar
_bar:
LFB2:
pushq   %rbp
LCFI0:
movq    %rsp, %rbp
LCFI1:
movl    $0, _foo(%rip) //foo变量赋初值为0
.align 4,0×90
L2:
movl    _foo(%rip), %eax
//必须force做memory的load操作。从而在多CPU下,或者系统
//中有其他逻辑,例如DMA,FPGA操作的情况下,CPU能感知
cmpl    $255, %eax
jne     L2 //如果变量有变化,函数返回
leave//离开while控制逻辑
ret
(没有打分)

雁过留声

“对李先静文章中volatile变量的量化小分析”有26个回复

  1. WR 于 2011-10-21 9:34 上午

    Very instructive.
    不过一直不明白为啥x86-64对全局变量要基于%rip寻址

  2. kevint 于 2011-10-21 10:44 上午

    int *p=0×1234;

    *p=1;

    *p=2;

    第一次的写操作会被优化掉。
    如果p是内存,perfectly right
    如果p是寄存器。。。恩。。。这种问题已经修理过无数新手了。。

  3. cheng 于 2011-10-21 4:05 下午

    @1. WR 于 2011-10-21 9:34 上午

    看看 -fPIC, position independent code; 安全相关的、内存地址随机化方面的文章

    在x86-32上,编译executable时缺省是不打开-fPIC,全局变量以静态寻址,加载于固定的虚拟地址处;这样一直用了很多年;但 “Smashing The Stack For Fun And Profit” 之后,诞生了很多利用这种全局变量在固定地址处的弱点的攻击;

    Current issue : #49 | Release date : 08/11/1996 | Editor : daemon9
    http://www.phrack.org/issues.html?issue=49&id=14#article

    编译shared object时、以及高版本gcc开始有了-fPIE支持,position independent executable, 编译executable时,全局的数据也可以由loader加载于任意虚拟地址了,但代价是程序执行效率的降低,因为没有%eip寻址,需要-fPIE生成代码将随机化过的地址先加载于某其它寄存器中、再进行二次寄存器寻址才能取得数据;有相关paper提到是执行效率下降30%;

    在x86-64上,因为有了%rip偏移寻址的天然支持,全局变量可以真正随机放置于任意虚拟地址上,只要数据段与代码段之间的偏移没变。
    这应该是在当年AMD64被设计时,就已经有考虑这层安全问题了。

  4. 陈怀临 于 2011-10-21 5:02 下午

    解释的很好。在MIPS里,类似与在编译时打开G optoion。例如,可以G1,G2,G4等。从而可以吧char,short,integer的全局变量通过Gloal offset的access。一个指令就把value读进来了。否则,你还要load base;then read value。就2个指令了。

  5. kevint 于 2011-10-21 6:04 下午

    that goes to sdata and sbss i think

  6. oldbee 于 2011-10-22 1:44 上午

    个人理解,即便是在单CPU,没有其它硬件(DMA/FPGA)的情况下,volatile有时也是必要的。
    比如:线程和中断共享的变量,线程置0,ISR置1,如果不用volatile的话,也有可能被编译器优化成死循环。
    所以,如果在不同”上下文”中共享变量的话,最好使用volatile。上下文包括但不限于,多线程/中断/硬件等。
    另外在主CPU和硬件间共享内存的话,还要小心cache的问题。
    至于对register的访问,我习惯做成宏,类似于:
    #define write32(addr,val) ((*(volatile u32_t *)(addr)) = (u32_t )(val))
    多线程间volatile的问题,冠诚有篇文章讲的很清楚,http://www.parallellabs.com/2010/12/04/why-should-we-be-care-of-volatile-keyword-in-multithreaded-applications/

  7. 胡不才 于 2011-10-22 7:58 上午

    首席,对于第一个程序,如果再添一个函数,里面修改foo的值,那是不是编译器编出来的就是另外的结果了?如果还不行,我就加pthread,在另一个线程里访问它。

  8. multithreaded 于 2011-10-22 8:01 上午

    C/C++作为系统级语言,它们与硬件的联系是很紧密的。volatile的意思是“易变的”,这个关键字最早就是为了针对那些“异常”的内存操作而准备的。它的效果是让编译器不要对这个变量的读写操作做任何优化,每次读的时候都直接去该变量的内存地址中去读,每次写的时候都直接写到该变量的内存地址中去,即不做任何缓存优化。

    All most correct except for the last sentence about CACHE since cache is under HW control and it has nothing to do with compiler or SW optimizations.

  9. multithreaded 于 2011-10-22 8:06 上午

    #7, it should be the same since when a compiler performs data-flow analysis, called constant propagation, within function bar, variable foo is always ZERO.

  10. 胡不才 于 2011-10-22 8:29 上午

    #9,我以前写过类似的程序,用gcc没有出错。

    foo is always zero没问题,但现在的问题是是否有死循环:L2: go L2.

  11. multithreaded 于 2011-10-22 8:33 上午

    A compiler doesn’t do such loop-forever checking during code generation since it is a runtime behavour.

  12. 胡不才 于 2011-10-22 8:41 上午

    #11 我想compiler出来的代码应该
    L2: jmp L2 if foo not 255

    回头我试试

  13. 胡不才 于 2011-10-22 10:34 上午

    [sage@localhost tektalk]$ objdump -d test.o

    test.o: file format elf64-x86-64

    Disassembly of section .text:

    0000000000000000 :
    0: 55 push %rbp
    1: 48 89 e5 mov %rsp,%rbp
    4: c7 05 00 00 00 00 00 movl $0×0,0×0(%rip) # e
    b: 00 00 00
    e: 90 nop
    f: 8b 05 00 00 00 00 mov 0×0(%rip),%eax # 15
    15: 3d ff 00 00 00 cmp $0xff,%eax
    1a: 75 f3 jne f
    1c: 5d pop %rbp
    1d: c3 retq

  14. 胡不才 于 2011-10-22 10:56 上午

    格式全乱了,没找到如何在comments中加html的方法,再试一下
    0000000000000000 :
    00: push %rbp
    01: mov %rsp,%rbp
    04: movl $0×0,0×0(%rip) # e
    0b:
    0e: nop
    0f: mov 0×0(%rip),%eax # 15
    15: cmp $0xff,%eax
    1a: jne f
    1c: pop %rbp
    1d: retq

  15. 胡不才 于 2011-10-22 11:00 上午

    上贴是首席第一段程序在fedora15上gcc 4.6.1的编译结果
    gcc -o test.o test.c

  16. multithreaded 于 2011-10-22 2:44 下午

    You forget to use option “-O2″. Please try again.

  17. 陈怀临 于 2011-10-22 4:00 下午

    多线程,我刚才拿O2做了一些B超。体检结果不太好。杯具了。。。我是在iMac下做的:-)

  18. multithreaded 于 2011-10-22 6:30 下午

    It may depdends on the version of gcc used.

    The version I used is 4.3.4

  19. multithreaded 于 2011-10-22 6:35 下午

    I also tested gcc version 4.4.4 20100503 (Red Hat 4.4.4-2) (GCC) on a 32bit Linux, the result is the same in which

    .L2:
    jmp .L2

    is generated.

  20. multithreaded 于 2011-10-22 6:38 下午

    The command used is:

    gcc -O2 -S foo.c -o foo.S

  21. 陈怀临 于 2011-10-23 5:16 上午

    多线程,你是对的。我今天早晨没有做对O2。否则,传说中的并行编译还真出现了。。。

    .globl _bar
    _bar:
    LFB2:
    pushq %rbp
    LCFI0:
    movq %rsp, %rbp
    LCFI1:
    movl $0, _foo(%rip)
    L2:
    jmp L2
    LFE2:
    .align 4,0×90
    .globl _bar1
    _bar1:
    LFB3:
    pushq %rbp
    LCFI2:
    movq %rsp, %rbp
    LCFI3:
    movl $0, _foo(%rip)
    L6:
    jmp L6
    LFE3:
    .align 4,0×90

  22. 游客 于 2011-10-24 8:05 下午

    试了下,在rhel5上面无该问题:
    Using built-in specs.
    Target: x86_64-redhat-linux
    Thread model: posix
    gcc version 4.1.2 20080704 (Red Hat 4.1.2-44)

  23. 陈怀临 于 2011-10-25 4:08 下午

    你把汇编贴出来。大家看看。

  24. kevint 于 2011-10-25 4:28 下午

    22楼估计没O2.
    这玩意跟OS没关系。编译器干的坏事

  25. kevint 于 2011-10-25 5:07 下午

    05年玩一个arm板,按键中断送上来,ISR把用户的键值写到全局变量。main while(1)读全局变量做处理。可想而知,程序死循环按键无反应。。。被这种问题折腾一次,永远也忘不了volatile。。。

    一晃N年过去。。。新菜鸟变老菜鸟。。。

  26. julang3 于 2011-10-26 1:31 上午

    仅仅看这个程序,其实编译器的优化是没问题的;
    C语言里面static定义的变量,只能在本文件中引用;