简体   繁体   English

我的(AT&T)汇编(x86-x64)代码应该增加但不增加

[英]My (AT&T) assembly (x86-x64) code should increment but doesn't

I'm trying to make a small program in assembly (for AT&T). 我正在尝试组装一个小程序(用于AT&T)。 I'm trying to get an input from the user in the form of an integer, increment it after that and then output the incremented value. 我正在尝试以整数形式从用户那里获得输入,在此之后增加它,然后输出增加的值。 However, the value doesn't increment. 但是,该值不会增加。 I've spent the last hours trying everything I could come up with, but it still doesn't work, so I have the idea that I maybe understand a concept in assembly not well, causing me to not spot the mistake. 我花了最后几个小时尝试所有可能的方法,但是仍然无法正常工作,所以我有一个想法,就是我可能对汇编中的一个概念不太了解,导致我没有发现错误。 This is my code: 这是我的代码:

1 hiString: .asciz "Hi\n"
  2 formatstr: .asciz "%ld"
  3 
  4 .global main
  5 
  6 main:
  7     movq $0, %rax           #no vector registers printf
  8     movq $hiString, %rdi    #load hiString
  9     call printf             #printf
 10     call inout              #inout
 11     movq $0, %rdi           #loading exit value into register rdi
 12     call exit               #exit
 13 
 14 inout:
 15     pushq %rbp              #Pushing bp
 16     movq %rsp, %rbp         #Moving sp to bp
 17     subq $8, %rsp           #Space on stack for variable
 18     leaq -8(%rbp), %rsi
 19     movq $formatstr, %rdi   #1st argument scanf
 20     movq $0, %rax           #no vector for scanf registers
 21     call scanf              #scanf
 22     incq %rsi
 23     call printf

From a tutorial I got of a friend of mine, I learned that lines 17 to 19 are necessary, however, I think I don't use the stack space I adress there, so I suspect the error having something with that. 从我的一个朋友那里获得的教程中,我知道第17至19行是必要的,但是,我认为我不使用我在那儿找到的堆栈空间,因此我怀疑该错误与之有关。 I'm not sure ofcourse. 当然,我不确定。 Thank you in advance. 先感谢您。

EDIT, UPDATED CODE (printf is still called in the subroutine now) 编辑,更新代码(现在仍在子例程中调用printf)

    1 hiString: .asciz "hi\n"
  2 formatstr: .asciz "%ld"
  3 
  4 .global main
  5 
  6 main:
  7     movq $0, %rax          
  8     movq $hiString, %di   
  9     call printf             
 10     call inout              
 11     movq $0, %rdi           
 12     call exit               
 13 
 14 inout:
 15     pushq %rbp             
 16     movq %rsp, %rbp         
 17     subq $8, %rsp         
 18     leaq -8(%rbp), %rsi
 19     movq $formatstr, %rdi   
 20     movq $0, %rax           
 21     call scanf              
 22     popq %rax
 23     incq %rax
 24     movq %rax, %rsi
 25     movq $0, %rax
 26     call printf
 27     addq $8, %rs  

It runs and increments now, however, when the incremented value is outputed, there show up some weird signs after the value. 它现在运行并递增,但是,当输出递增的值时,该值之后会出现一些奇怪的符号。

Edit: Nevermind, the above only happened once, now there is no incremented value outputted, only weird signs. 编辑:没关系,上面只发生过一次,现在没有输出增量值,只有怪异的迹象。

This is an assembly-level version of the classic confusion about how to call scanf correctly. 这是有关如何正确调用scanf的经典混淆的汇编级版本。

 14 inout:
 15     pushq %rbp              #Pushing bp
 16     movq %rsp, %rbp         #Moving sp to bp
 17     subq $8, %rsp           #Space on stack for variable
 18     leaq -8(%rbp), %rsi
 19     movq $formatstr, %rdi   #1st argument scanf
 20     movq $0, %rax           #no vector for scanf registers
 21     call scanf              #scanf

(Editor's note: prefer either mov $formatstr, %edi in a Linux non-PIE executable, or more portably position-independent lea formatstr(%rip), %rdi to put the address of a string in static storage into a register). (编者注:最好是mov $formatstr, %edi Linux非PIE可执行文件中的mov $formatstr, %edi ,或者更可移植的与位置无关的lea formatstr(%rip), %rdi将静态存储中的字符串地址放入寄存器中)。

Up to this point your code is correct (except that you haven't aligned the stack correctly, but don't worry about that right now, scanf will probably let you get away with it). 到目前为止,您的代码是正确的(除非您没有正确对齐堆栈,但是现在不必担心, scanf可能会让您摆脱它)。 Update: modern builds of glibc do have a scanf that faults on a misaligned RSP , since Ubuntu 18.04 for example, maybe earlier. 更新:现代版本的glibc确实存在scanf,该错误会在未对齐的RSP上发生故障 ,例如从Ubuntu 18.04开始,可能更早。

 22     incq %rsi

Here's where you go wrong. 这是您出问题的地方。 Before the call you set RSI (the second argument register for scanf ) to be a pointer to a storage location. 在调用之前,将RSI( scanf的第二个参数寄存器)设置为指向存储位置的指针 scanf read a number from stdin and wrote it to that storage location , not to RSI. scanf从stdin读取一个数字并将其写入该存储位置 ,而不是RSI。

From the discussion in the comments, your intention is to add one to the value read by scanf and immediately print it back out. 从评论的讨论中,您的意图是将一个值添加到scanf读取的值中,然后立即将其打印出来。 As several other people pointed out, after scanf returns, you cannot assume that the values you loaded into RSI, RDI, or RAX are intact. 正如其他一些人指出的那样,在scanf返回之后,您不能假定加载到RSI,RDI或RAX中的值是完整的。 (The x86-64 psABI specifies which registers are to be preserved over a function call: of the integer registers, only RBX, RBP, and R12 through R15 are preserved. You should read this document cover to cover if you intend to do much assembly programming on x86-64. (Caution: Windows uses a different ABI whose calling convention is documented on MSDN, see links in the x86 tag wiki .)) x86-64 psABI指定通过函数调用保留哪些寄存器:在整数寄存器中,仅保留RBX,RBP和R12至R15。如果您打算进行大量汇编,则应阅读本文档的封面,以了解更多信息。在x86-64上进行编程(警告:Windows使用不同的ABI,其调用约定已记录在MSDN上,请参阅x86标签wiki中的链接。)

So you must set up the args to printf from scratch because scanf destroyed those registers: 因此,您必须将args从头设置为printf ,因为scanf破坏了这些寄存器:

       movq -8(%rbp), %rsi   # load variable as arg 2 of printf
       incq %rsi             # and add one
       movq $formatstr, %rdi # first argument to printf
       xorl %rax, %rax       # no vector args to printf
       call printf

Pay close attention to the difference between scanf and printf here: you can use the same format string for both, but when you call scanf you pass the address of a storage location ( leaq -8(%rbp), %rsi ), whereas when you call printf you pass the value to be printed ( movq -8(%rbp), %rsi; incq %rsi ). 请在此处密切注意scanfprintf之间的区别:您可以为两者使用相同的格式字符串,但是当调用scanf您传递存储位置地址leaq -8(%rbp), %rsi ),而当您调用printf传递要打印movq -8(%rbp), %rsi; incq %rsi )。

(In fact you ought to use a slightly different format string when you call printf , because you need to print a newline after the number, so "%ld\\n" would be better.) (实际上,在调用printf ,您应该使用略有不同的格式字符串,因为您需要在数字后打印换行符,因此"%ld\\n"会更好。)

Your current code does almost this, in a different way. 您当前的代码几乎以不同的方式完成此任务。 I do it this way because it's bad practice to mess with the stack pointer ( popq %rax ) in the middle of a function. 我这样做是因为在函数中间弄乱堆栈指针( popq %rax )是一种不好的做法。 (Remember what I said about not aligning the stack correctly? It's much easier to keep the stack aligned if you set up a complete "call frame" on entry and then leave the stack pointer alone until exit. Technically you are only required to have the stack pointer aligned at the point of each call instruction, though.) (还记得我说过的关于未正确对齐堆栈的内容吗?如果在进入时设置一个完整的“调用框架”,然后不理会堆栈指针直到退出,则使堆栈保持对齐会容易得多。从技术上讲,您只需要具有不过,堆栈指针会在每个调用指令的位置对齐。)

You also don't end the function correctly: 您也没有正确结束该功能:

 27     addq $8, %rs  

I think you didn't copy and paste your entire program - this looks like it's been cut off in the middle of the line. 我认为您没有复制并粘贴整个程序-看起来好像已经在行中间被切断了。 Regardless, if you're going to bother having a frame pointer in the first place (frame pointers are not required on x86-64) you should use it again to exit: 无论如何,如果您首先要麻烦帧指针(x86-64上不需要帧指针),则应再次使用它退出:

        movq %rbp, %rsp
        popq %rbp
        ret

Incidentally, "AT&T" assembly syntax is used for many different CPU architectures. 顺便说一句,“ AT&T”汇编语法用于许多不同的CPU体系结构。 When talking about assembly language we always need to know the CPU architecture first ; 在谈到汇编语言,我们总是首先要知道CPU的架构; the syntax variant (if any) is secondary. 语法变体(如果有)是次要的。 You should have titled the question "My assembly program (x86-64, AT&T syntax) ..." 您应该将问题命名为“我的汇编程序(x86-64,AT&T语法)...”。


As a final piece of advice, I would suggest you compile this C program 作为最后的建议,我建议您编译此C程序

#include <stdio.h>

static void inout(void)
{
    long x;
    scanf("%ld", &x);
    printf("%ld\n", x+1);
}

int main(void)
{
    printf("hi\n");
    inout();
    return 0;
}

with your choice of C compiler, using options equivalent to -S -O2 -fno-inline (that is: generate textual assembly language, optimized, but don't do any inlining) and then read through the assembly output line by line. 使用您选择的C编译器时,请使用与-S -O2 -fno-inline等效的选项(即:生成文本汇编语言,进行了优化,但不进行任何内联),然后逐行读取汇编输出。 Whenever the C compiler does something different than you did, that probably means it knows something you don't know and you should learn about that something. 每当C编译器执行与您不同的操作时,这可能意味着C编译器知道您不知道的内容,因此您应该了解该内容。

Or more simply, look at it on the Godbolt compiler explorer 或更简单地说, 在Godbolt编译器资源管理器中查看它

re: updated code: 回复:更新的代码:

It runs and increments now, however, when the incremented value is outputed, there show up some weird signs after the value. 它现在运行并递增,但是,当输出递增的值时,该值之后会出现一些奇怪的符号。

Arg-passing registers are call-clobbered. 传递arg的寄存器被调用。 You call printf without putting the format-string into %rdi , which you have to assume holds garbage after scanf returns. 您可以在不将格式字符串放入%rdi情况下调用printf ,而在scanf返回后必须假定该字符串保留垃圾。

Single-step your code with a debugger. 使用调试器单步执行代码。 Use ni to step over call s in gdb. 使用ni结束gdb中的call (See the bottom of the tag wiki for GDB tips). (有关GDB技巧,请参见标签Wiki的底部)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM