非常简单的程序集介绍代码。
看起来通过gcc -o prog1 prog1.s
编译是可以的,但是./prog1
只是跳过一行,什么都不显示,就像等待一个代码不要求的输入一样。
在VMware上运行的64位gNewSense中使用gcc(Debian 4.7.2-5)4.7.2。代码:
/*
int nums[] = {10, -21, -30, 45};
int main() {
int i, *p;
for (i = 0, p = nums; i != 4; i++, p++)
printf("%d\n", *p);
return 0;
}
*/
.data
nums: .int 10, -21, -30, 45
Sf: .string "%d\n" # string de formato para printf
.text
.globl main
main:
/********************************************************/
/* mantenha este trecho aqui e nao mexa - prologo !!! */
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rbx, -8(%rbp)
movq %r12, -16(%rbp)
/********************************************************/
movl $0, %ebx /* ebx = 0; */
movq $nums, %r12 /* r12 = &nums */
L1:
cmpl $4, %ebx /* if (ebx == 4) ? */
je L2 /* goto L2 */
movl (%r12), %eax /* eax = *r12 */
/*************************************************************/
/* este trecho imprime o valor de %eax (estraga %eax) */
movq $Sf, %rdi /* primeiro parametro (ponteiro)*/
movl %eax, %esi /* segundo parametro (inteiro) */
call printf /* chama a funcao da biblioteca */
/*************************************************************/
addl $1, %ebx /* ebx += 1; */
addq $4, %r12 /* r12 += 4; */
jmp L1 /* goto L1; */
L2:
/***************************************************************/
/* mantenha este trecho aqui e nao mexa - finalizacao!!!! */
movq $0, %rax /* rax = 0 (valor de retorno) */
movq -8(%rbp), %rbx
movq -16(%rbp), %r12
leave
ret
/***************************************************************/
1条答案
按热度按时间nwwlzxa71#
tl;dr:在
call printf
之前执行xorl %eax, %eax
。printf
是一个varargs函数。以下是System V AMD 64 ABI对varargs函数的说明:对于可能调用使用varargs或stdargs的函数的调用(无原型调用或对声明中包含省略号(. . .)的函数的调用)
%al
18用作隐藏参数以指定所使用的向量寄存器的数目。%al
的内容不需要与寄存器的数目精确匹配,但是必须是所使用的向量寄存器的数量的上限,并且在0-8的范围内。你打破了这个规则,你会看到你的代码第一次调用
printf
时,%al
是10,这大于上限8。在你的gNewSense系统上,这里有一个printf
开头的反汇编:重要位的准C翻译是
goto *(&&after_movaps - al * 4);
。为了提高效率,gcc和/或glibc不想保存比您使用的更多的向量寄存器,也不想执行一堆条件分支。每个保存向量寄存器的指令是4个字节,所以它取向量寄存器保存指令的结尾,减去al * 4
个字节,然后跳到那里。这导致刚好有足够的指令执行。由于你有超过8个,它最终跳到太远的后面,并在它刚刚采取的跳转指令之前着陆,从而创建了一个无限循环。至于为什么它不能在现代系统上重现,这里有一个他们的
printf
的开始拆解:一个重要位的准C翻译是
if(!al) goto after_movaps;
。为什么会改变呢?我猜是Spectre。Spectre的缓解措施使间接跳转变得非常慢,所以不再值得使用这个技巧了。* 或者不需要;* 相反,他们做了一个简单得多的检查:如果有任何向量寄存器,则保存它们。对于这段代码,错误的al
值并不是灾难,因为它只是意味着向量寄存器将被不必要地复制。