对李先静文章中volatile变量的量化小分析
作者 陈怀临 | 2011-10-21 06:02 | 类型 科技普及 | 26条用户评论 »
下面是一段简单的代码,试图对李先静文章中的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个回复
Very instructive.
不过一直不明白为啥x86-64对全局变量要基于%rip寻址
int *p=0×1234;
*p=1;
*p=2;
第一次的写操作会被优化掉。
如果p是内存,perfectly right
如果p是寄存器。。。恩。。。这种问题已经修理过无数新手了。。
@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被设计时,就已经有考虑这层安全问题了。
解释的很好。在MIPS里,类似与在编译时打开G optoion。例如,可以G1,G2,G4等。从而可以吧char,short,integer的全局变量通过Gloal offset的access。一个指令就把value读进来了。否则,你还要load base;then read value。就2个指令了。
that goes to sdata and sbss i think
个人理解,即便是在单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/
首席,对于第一个程序,如果再添一个函数,里面修改foo的值,那是不是编译器编出来的就是另外的结果了?如果还不行,我就加pthread,在另一个线程里访问它。
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.
#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.
#9,我以前写过类似的程序,用gcc没有出错。
foo is always zero没问题,但现在的问题是是否有死循环:L2: go L2.
A compiler doesn’t do such loop-forever checking during code generation since it is a runtime behavour.
#11 我想compiler出来的代码应该
L2: jmp L2 if foo not 255
回头我试试
[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
格式全乱了,没找到如何在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
上贴是首席第一段程序在fedora15上gcc 4.6.1的编译结果
gcc -o test.o test.c
You forget to use option “-O2″. Please try again.
多线程,我刚才拿O2做了一些B超。体检结果不太好。杯具了。。。我是在iMac下做的:-)
It may depdends on the version of gcc used.
The version I used is 4.3.4
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.
The command used is:
gcc -O2 -S foo.c -o foo.S
多线程,你是对的。我今天早晨没有做对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
试了下,在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)
你把汇编贴出来。大家看看。
22楼估计没O2.
这玩意跟OS没关系。编译器干的坏事
05年玩一个arm板,按键中断送上来,ISR把用户的键值写到全局变量。main while(1)读全局变量做处理。可想而知,程序死循环按键无反应。。。被这种问题折腾一次,永远也忘不了volatile。。。
一晃N年过去。。。新菜鸟变老菜鸟。。。
仅仅看这个程序,其实编译器的优化是没问题的;
C语言里面static定义的变量,只能在本文件中引用;