我试图了解简单C函数的底层程序集。
program1.c
void function() {
char buffer[1];
}
=>
push %ebp
mov %esp, %ebp
sub $0x10, %esp
leave
ret
不确定它是如何到达0x10的?不是字符1字节,即8位,所以它应该是0x08?
program2.c
void function() {
char buffer[4];
}
=>
push %ebp
mov %esp, %ebp
sub $0x18, %esp
mov ...
mov ...
[a bunch of random instructions]
不确定它是如何到达0x18的?另外,为什么在SUB
指令之后有那么多附加指令?我所做的就是将数组的长度从1改为4。
答案 0 :(得分:6)
gcc uses -mpreferred-stack-boundary=4
by default适用于x86 32和64位ABI,因此它会保持{
"$type": "Tfl.Api.Presentation.Entities.PlacesResponse, Tfl.Api.Presentation.Entities",
"centrePoint": [
51.555,
0.059
],
"places": [{
"$type": "Tfl.Api.Presentation.Entities.StopPoint, Tfl.Api.Presentation.Entities",
"naptanId": "490009219W",
"indicator": "Stop B",
"stopLetter": "B",
"modes": [
"bus"
],
"icsCode": "1009219",
"stopType": "NaptanPublicBusCoachTram",
"stationNaptan": "490G00009219",
"lines": [{
"$type": "Tfl.Api.Presentation.Entities.Identifier, Tfl.Api.Presentation.Entities",
"id": "25",
"name": "25",
"uri": "/Line/25",
"type": "Line"
}, {
"$type": "Tfl.Api.Presentation.Entities.Identifier, Tfl.Api.Presentation.Entities",
"id": "86",
"name": "86",
"uri": "/Line/86",
"type": "Line"
}, {
"$type": "Tfl.Api.Presentation.Entities.Identifier, Tfl.Api.Presentation.Entities",
"id": "w19",
"name": "W19",
"uri": "/Line/w19",
"type": "Line"
}],
"lineGroup": [{
"$type": "Tfl.Api.Presentation.Entities.LineGroup, Tfl.Api.Presentation.Entities",
"naptanIdReference": "490009219W",
"stationAtcoCode": "490G00009219",
"lineIdentifier": [
"25",
"86",
"w19"
]
}],
"lineModeGroups": [{
"$type": "Tfl.Api.Presentation.Entities.LineModeGroup, Tfl.Api.Presentation.Entities",
"modeName": "bus",
"lineIdentifier": [
"25",
"86",
"w19"
]
}],
"status": true,
"id": "490009219W",
"commonName": "Little Ilford Lane",
"distance": 64.10041498232529,
"placeType": "StopPoint",
"additionalProperties": [{
"$type": "Tfl.Api.Presentation.Entities.AdditionalProperties, Tfl.Api.Presentation.Entities",
"category": "Direction",
"key": "CompassPoint",
"sourceSystemKey": "Naptan490",
"value": "W"
}, {
"$type": "Tfl.Api.Presentation.Entities.AdditionalProperties, Tfl.Api.Presentation.Entities",
"category": "Direction",
"key": "Towards",
"sourceSystemKey": "CountDown",
"value": "East Ham or Manor Park"
}],
"lat": 51.554475,
"lon": 0.059381
}]
}
16B对齐。
我能够使用gcc 4.8.2 -O0 -m32
on the Godbolt Compiler Explorer
%esp
您必须使用默认启用的gcc with -fstack-protector
版本。较新的gcc通常不配置为执行此操作,因此您不会获得相同的sentinel值并检查写入堆栈。 (在那个Godbolt链接中尝试更新的gcc)
void f1() { char buffer[1]; }
pushl %ebp
movl %esp, %ebp # make a stack frame (`enter` is super slow, so gcc doesn't use it)
subl $16, %esp
leave # `leave` is not terrible compared to mov/pop
ret
显然gcc认为void f4() { char buffer[4]; }
pushl %ebp #
movl %esp, %ebp # make a stack frame
subl $24, %esp # IDK why it reserves 24, rather than 16 or 32B, but prob. has something to do with aligning the stack for the possible call to __stack_chk_fail
movl %gs:20, %eax # load a value from thread-local storage
movl %eax, -12(%ebp) # store it on the stack
xorl %eax, %eax # tmp59
movl -12(%ebp), %eax # D.1377, tmp60
xorl %gs:20, %eax # check that the sentinel value matches what we stored
je .L3 #,
call __stack_chk_fail #
.L3:
leave
ret
是“易受攻击的对象”,而不是char buffer[4]
。没有char buffer[1]
,即使在-fstack-protector
,asm也几乎没有差异。
答案 1 :(得分:2)
不是字符1字节,即8位,所以它应该是0x08?
这个值不是位,而是字节。
不确定它是如何到达0x10的?
这一行:
push %ebp
mov %esp, %ebp
sub $0x10, %esp
在堆栈上分配空间,保留16个字节的内存用于执行此功能。
存储以下信息需要所有这些字节:
ret
指令在您的示例中,分配了16个字节。其中4个用于将被调用的下一条指令的地址,因此我们剩下12个字节。 1个字节用于大小为1的char数组,可能由编译器优化为单个char。最后11个字节可能存储了一些我记不住的东西,并且编译器添加了填充。
不确定它是如何到达0x18的?
第二个示例中的每个附加字节增加了2个字节的堆栈大小,char的1个字节,以及1个用于内存对齐的字节。
另外,为什么SUB指令后会有这么多附加指令?
请按照说明更新问题。
答案 2 :(得分:2)
此代码只是设置堆栈帧。这用作局部变量的临时空间,并且会有某种对齐要求。
您还没有提到您的平台,所以我无法确切地告诉您系统的要求是什么,但显然这两个值至少是8字节对齐的(所以局部变量的大小是四舍五入所以%esp仍然是8的倍数。
搜索“c function prolog epilog”或“c function call stack”以查找此区域的更多资源。
编辑 - Peter Cordes的回答解释了差异和神秘的额外说明。
为了完整起见,尽管法比奥已经回答了这一部分:
不确定它是如何到达0x10的?不是字符1字节,即8位,所以它应该是0x08?
在x86上,%esp
是堆栈指针,指针存储地址,这些是字节地址。子字节寻址很少使用(参见Peter的评论)。如果要检查字节内的各个位,通常会对值使用按位(&
,|
,~
,^
)运算,但不会更改地址。
(你可以同样认为,子缓存行寻址是一个方便的小说,但我们正在迅速脱离主题)。
答案 3 :(得分:-1)
每当你分配内存时,你的操作系统几乎从来没有真正给你那个数量,除非你使用像pvalloc
这样的函数,它给你一个页面对齐的字节数(通常是4K)。相反,您的操作系统假定您将来可能需要更多,所以继续并为您提供更多。
要禁用此行为,请使用不执行缓冲的较低级别系统调用,例如sbrk()
。这些讲义是一个很好的资源:
http://web.eecs.utk.edu/~plank/plank/classes/cs360/360/notes/Malloc1/lecture.html