gcc - 程序集可执行文件不显示任何内容 (x64)
问题描述
很简单的汇编介绍代码。
似乎可以通过 编译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
/***************************************************************/
解决方案
tl;博士:做xorl %eax, %eax
之前call printf
。
printf
是一个可变参数函数。以下是 System V AMD64 ABI 关于可变参数函数的说明:
对于可能调用使用 varargs 或 stdargs 的函数的调用(无原型调用或对声明中包含省略号 (...) 的函数的调用),
%al
18用作隐藏参数以指定使用的向量寄存器的数量。的内容%al
不需要完全匹配寄存器的数量,但必须是使用的向量寄存器数量的上限,并且在 0-8 范围内。
你打破了那个规则。您会看到您的代码第一次调用printf
,%al
是 10,超过了 8 的上限。在您的 gNewSense 系统上,这里是 的开头的反汇编printf
:
printf:
sub $0xd8,%rsp
movzbl %al,%eax # rax = al;
mov %rdx,0x30(%rsp)
lea 0x0(,%rax,4),%rdx # rdx = rax * 4;
lea after_movaps(%rip),%rax # rax = &&after_movaps;
mov %rsi,0x28(%rsp)
mov %rcx,0x38(%rsp)
mov %rdi,%rsi
sub %rdx,%rax # rax -= rdx;
lea 0xcf(%rsp),%rdx
mov %r8,0x40(%rsp)
mov %r9,0x48(%rsp)
jmpq *%rax # goto *rax;
movaps %xmm7,-0xf(%rdx)
movaps %xmm6,-0x1f(%rdx)
movaps %xmm5,-0x2f(%rdx)
movaps %xmm4,-0x3f(%rdx)
movaps %xmm3,-0x4f(%rdx)
movaps %xmm2,-0x5f(%rdx)
movaps %xmm1,-0x6f(%rdx)
movaps %xmm0,-0x7f(%rdx)
after_movaps:
# nothing past here is relevant for your problem
重要位的准 C 翻译是goto *(&&after_movaps - al * 4);
. 为了提高效率,gcc 和/或 glibc 不想保存比您使用的更多的向量寄存器,它也不想做一堆条件分支。保存向量寄存器的每条指令都是 4 个字节,所以它取向量寄存器保存指令的结尾,减去al * 4
字节,然后跳转到那里。这导致执行的指令足够多。由于你有超过 8 个,它最终跳得太远,并在它刚刚接受的跳转指令之前着陆,从而创建了一个无限循环。
至于为什么它不能在现代系统上重现,这里是他们开头的反汇编printf
:
printf:
sub $0xd8,%rsp
mov %rdi,%r10
mov %rsi,0x28(%rsp)
mov %rdx,0x30(%rsp)
mov %rcx,0x38(%rsp)
mov %r8,0x40(%rsp)
mov %r9,0x48(%rsp)
test %al,%al # if(!al)
je after_movaps # goto after_movaps;
movaps %xmm0,0x50(%rsp)
movaps %xmm1,0x60(%rsp)
movaps %xmm2,0x70(%rsp)
movaps %xmm3,0x80(%rsp)
movaps %xmm4,0x90(%rsp)
movaps %xmm5,0xa0(%rsp)
movaps %xmm6,0xb0(%rsp)
movaps %xmm7,0xc0(%rsp)
after_movaps:
# nothing past here is relevant for your problem
重要位的准 C 翻译是if(!al) goto after_movaps;
. 为什么会发生这种变化?我的猜测是幽灵。Spectre 的缓解措施使间接跳跃非常缓慢,因此不再值得这样做。 或不; 看评论。相反,他们进行了更简单的检查:如果有任何向量寄存器,则将它们全部保存。使用此代码,您的错误值al
并不是灾难,因为它只是意味着向量寄存器将被不必要地复制。
推荐阅读
- .net - AWS弹性搜索服务返回403响应与.NET HttpClient-http请求标头中的错误大小写
- c - C 程序中的功能块架构
- c# - 如何获取经过身份验证的用户的访问令牌以进行授权的资源 api 调用?
- dart - 在 Dart 中显示 iframe
- java - 工作簿需要很长时间才能生成 excel 文件
- ios - iOS:购买时出现应用内自动更新订阅错误
- json - 如何通过 jq 在 json 中仅获取重复的键?
- azure - 我只想在项目的特定模块中使用 LUIS
- ruby-on-rails - 在 Rails 中使用 HTTP 协议发送电子邮件(MailGun API)
- angular - PrimeNG p-table 动态字幕