0

我编写了以下基本程序来添加两个数字,1+2如下:

.globl main

main:

    # put 1 (1 byte int/char) into accumulator register
    mov     $1,     %eax

    # add 2 (1 byte int/char), storing result in accumulator
    add     $2,     %eax

    # move the result of the accumulator into Data register (input/output)
    mov     %eax,   %edx

    ret

编译后,它会返回预期的输出:

$ gcc d.s -o d2.out && ./d2.out; echo $?
3

关于这个程序,我有几个问题:

  • 这或多或少是一个好的程序,还是我滥用了任何操作等?
  • 程序集文件是否总是必须具有一个globl功能,例如main,或者它是否可以删除main/.globl main部分并只是“逐行运行代码”?
  • 最后,查找操作码的最佳资源是什么?我倾向于用谷歌搜索这些,它会返回不同的结果:如果有一个标准资源就好了Python docs,我可以在其中为一个页面添加书签并在那里查看所有内容。
4

2 回答 2

3

mov到 EDX 是没有意义的,返回值寄存器是 AL / AX / EAX / RAX / RDX:RAX 对于 x86-64 上从 1 字节到 16 字节的宽度。EDX 或 RDX 仅涉及宽返回值,太宽而无法放入 RAX。(或者在 32 位模式下,在 EDX:EAX 寄存器对中返回 64 位值,因为没有 RAX。)

这适用于所有标准 x86 32 位和 x86-64 调用约定,包括 GNU/Linux 上使用的 i386 和 x86-64 System V ABI。


如果您正在编写一个main,或者您想从另一个文件调用的任何函数,它必须是一个.globl符号。 (除非您.include "foo.s"不是单独构建+链接。)这就是使其在符号表中可见的原因,以便链接器解析对它的引用。例如,从call main已经编译的代码中的a _start, incrt0.o或其他东西,如果你运行,你可以看到 gcc 链接gcc -v foo.S。(这是一个过度简化;glibc_start实际上将 main 的地址作为 arg 传递给__libc_start_main,它位于 中libc.so.6,因此有一些来自 libc 的代码在之前运行main。请参阅Linux x86 Program Start Up 或 - 我们到底是如何进入 main 的()? )

如果您正在制作一个没有 CRT 的静态可执行文件(定义_start而不是main制作您自己的exit_group系统调用),您只需将指令放入文件中,然后让链接器 ( ld) 选择节的顶部.text作为 ELF 入口点(如果没有)找不到_start符号。(readelf -a a.out用于查看此类信息。)

如果你只打算在 GDB 下运行程序来单步执行一些你感兴趣的指令,你甚至可以省略 exit-cleanly 部分。(为此,使用 GDB 的starti命令在第一条用户空间指令之前使用临时断点运行,因此您不必通过绝对地址手动设置断点(因为没有符号)。)

$ cat > foo.S
mov $1 + 2, %edi     # do the math at assemble time
mov $231, %eax         # _NR_exit_group
syscall

$ gcc -static -no-pie -nostdlib foo.S      # like as + ld manually
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000

$ ./a.out ; echo $?
3

$ strace ./a.out
execve("./a.out", ["./a.out"], 0x7ffe0706a3c0 /* 54 vars */) = 0
exit_group(3)                           = ?
+++ exited with 3 +++

如果您的系统是 32 位的,因此as默认为 32 位模式,请使用 32 位int $0x80和不同的寄存器。

最后,查找操作码的最佳资源是什么?

我通常将浏览器选项卡打开到https://www.felixcloutier.com/x86/,这是英特尔第 2 卷手册的 HTML 抓取。原始 PDF有一些关于如何阅读条目的介绍章节,所以如果您发现任何符号令人困惑,请查看它。英特尔手册中有一些旧版的遗漏了 SIMD 指令,所以这对我来说没用,但也许是你作为初学者想要的。

其他资源链接自x86 标签 wiki,包括http://ref.x86asm.net/coder64.html,它是按操作码而非助记符组织的,并且有快速参考列来提醒您指令是否读取或修改 FLAGS ,如果是这样,以及类似的东西。

于 2020-08-01T20:15:52.287 回答
2

这或多或少是一个好的程序,还是我滥用了任何操作等?

首先,是的。

然而,汇编是关于效率的,所以最后一条语句是不必要的:

mov     %eax,   %edx

程序集文件是否总是必须具有一个全局函数,例如 main

不必要。例如,它可以是您可以从 C/C++ 代码调用的其他函数。但是,如果您想从中生成可执行文件,则需要main或者_start如果您将其ld用作链接器。

“逐行运行代码”?

为此,您需要一个调试器。如果您想学习汇编,这将是最重要的事情。你会想看看寄存器,看看值是如何变化的,标志发生了什么等等。我给出了一个答案,解释了如何设置调试器和单步调试你的代码。组装时需要-g标志gcc来调试代码。

一个基本的例子:

  1. 编译-g
gcc -g file.s -o file
  1. tui以模式启动 gdb 。
> gdb --tui ./file
> start           # this will automatically start the program and break at main:
> layout regs     # show registers at the top (you will need this a lot)
> n               # next instruction
> si              # step into, when you use functions, si into function

在 gdb 中按 enter 将自动再次执行最后一个命令。这将使您免于n一遍又一遍地打字。还有一些命令:

> b 2      # break at line 2
> b func   # break at label func
> b main   # break at main

> print/x  $eax  # print value in eax in hex form, there are other /format specifiers, print/d (decimal), print/s string, print/t (binary)
> x/s $eax    # print string pointed to by eax

> info frame   # look at the current stack frame

这些是您需要的最常见的说明。您可以键入help command_name以获取有关命令的更多信息。并且有各种备忘单等可以帮助您解决这个问题。

如果你愿意,你也可以得到一个 gui,我个人不太喜欢它们。结帐 Nemiver,这非常好。gdbgui可以使用设置,pip但它对于调试 asm 并不是很好,因为看寄存器很痛苦。有ddd一个我最喜欢的,但它的 gui 是 1970 年代的,所以......

最后,查找操作码的最佳资源是什么?

最好的资源是英特尔手册,但是如果您刚刚开始阅读,它们可能有点难以阅读。我会推荐Felix Cloutier 的 x86 asm 参考x86标签 wiki中有很多信息和参考资料。

您可能还想阅读Linux 的调用约定并查找您将非常需要的Linux 系统调用。如果您打算编程或只是想了解更多关于计算机的知识,我强烈建议您阅读Programming from the Ground Up一书,该书可免费获得并使用 AT&T 风格的程序集。然而它有点过时了,所以你必须用谷歌搜索。它有一个包含常见 x86 指令的附录,这将非常有帮助。

于 2020-08-01T20:04:15.523 回答