你好,使用 Linux 系统调用的汇编语言世界?

2024-10-11 08:36:00
admin
原创
74
摘要:问题描述:我知道这int 0x80在 Linux 中是中断。但是,我不明白这段代码是如何工作的。它会返回某些东西吗?$ - msg代表什么?global _start section .data msg db "Hello, world!", 0x0a l...

问题描述:

  1. 我知道这int 0x80在 Linux 中是中断。但是,我不明白这段代码是如何工作的。它会返回某些东西吗?

  2. $ - msg代表什么?

global _start

section .data
    msg db "Hello, world!", 0x0a
    len equ $ - msg

section .text
_start:
    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, len
    int 0x80 ;What is this?
    mov eax, 1
    mov ebx, 0
    int 0x80 ;and what is this?

解决方案 1:

$ 在 NASM 中究竟是如何工作的?解释了如何$ - msg让 NASM 将字符串长度计算为汇编时间常量,而不是对其进行硬编码。


系统调用是通过将参数放入寄存器,然后运行int 0x80(32 位模式)或syscall(64 位模式)来完成的。i386 和 x86-64 上的 UNIX 和 Linux 系统调用(和用户空间函数)的调用约定是什么以及Linux 系统调用权威指南。

将其视为int 0x80一种跨越用户/内核权限边界的“调用”内核的方式。 内核根据int 0x80执行时寄存器中的值执行操作,然后最终返回。返回值在 EAX 中。

当执行到达内核的入口点时,它会查看 EAX 并根据 EAX 中的调用号分派到正确的系统调用。来自其他寄存器的值作为函数参数传递给内核的该系统调用处理程序。(例如 eax=4 /int 0x80将使内核调用其sys_write内核函数,从而实现 POSIXwrite系统调用。)

另请参见如果在 64 位代码中使用 32 位 int 0x80 Linux ABI 会发生什么情况? - 该答案包括查看内核入口点中被 调用 的 asm int 0x80。 (也适用于 32 位用户空间,而不仅仅是您不应该使用的 64 位int 0x80)。


如果您还不了解低级 Unix 系统编程,您可能只想用 asm 编写函数,这些函数接受参数并返回一个值(或通过指针参数更新数组),然后从 C 或 C++ 程序中调用它们。然后,您只需担心学习如何处理寄存器和内存,而无需学习 POSIX 系统调用 API 和使用它的 ABI。这也使得将您的代码与 C 实现的编译器输出进行比较变得非常容易。编译器通常在编写高效代码方面做得相当不错,但很少是完美的。

libc 为系统调用提供了包装函数,因此编译器生成的代码将call write不需要直接使用 来调用它int 0x80(或者,如果您关心性能,sysenter)。(在 x86-64 代码中,对于 64 位 ABI使用syscall。)另请参阅syscalls(2)

系统调用记录在第 2 节手册页中,例如write(2)。有关 libc 包装器函数和底层 Linux 系统调用之间的差异,请参阅注释部分。请注意,的包装器sys_exit_exit(2),而不是exit(3)首先刷新 stdio 缓冲区和其他清理工作的 ISO C 函数。实际上_exit()使用结束所有线程exit_group的系统调用。 也使用它,因为单线程进程没有任何缺点。exit(3)

给定系统调用约定和 C 手册页,您可以看到哪个参数进入哪个寄存器;有些网页包含系统调用和寄存器的表,但您不需要它们。

此代码进行了 2 个系统调用:

  • `sys_write(1, "Hello, World!
    ", sizeof(...));`

  • sys_exit(0);

我对其进行了大量注释(注释到在没有彩色语法高亮的情况下,注释开始模糊实际代码的程度)。这是试图向初学者指出问题,而不是您正常注释代码的方式。

section .text             ; Executable code goes in the .text section
global _start             ; The linker looks for this symbol to set the process entry point, so execution start here
;;;a name followed by a colon defines a symbol.  The global _start directive modifies it so it's a global symbol, not just one that we can CALL or JMP to from inside the asm.
;;; note that _start isn't really a "function".  You can't return from it, and the kernel passes argc, argv, and env differently than main() would expect.
 _start:
    ;;; write(1, msg, len);
    ; Start by moving the arguments into registers, where the kernel will look for them
    mov     edx,len       ; 3rd arg goes in edx: buffer length
    mov     ecx,msg       ; 2nd arg goes in ecx: pointer to the buffer
    ;Set output to stdout (goes to your terminal, or wherever you redirect or pipe)
    mov     ebx,1         ; 1st arg goes in ebx: Unix file descriptor. 1 = stdout, which is normally connected to the terminal.

    mov     eax,4         ; system call number (from SYS_write / __NR_write from unistd_32.h).
    int     0x80          ; generate an interrupt, activating the kernel's system-call handling code.  64-bit code uses a different instruction, different registers, and different call numbers.
    ;; eax = return value, all other registers unchanged.

    ;;;Second, exit the process.  There's nothing to return to, so we can't use a ret instruction (like we could if this was main() or any function with a caller)
    ;;; If we don't exit, execution continues into whatever bytes are next in the memory page,
    ;;; typically leading to a segmentation fault because the padding 00 00 decodes to  add [eax],al.

    ;;; _exit(0);
    xor     ebx,ebx       ; first arg = exit status = 0.  (will be truncated to 8 bits).  Zeroing registers is a special case on x86, and mov ebx,0 would be less efficient.
                      ;; leaving out the zeroing of ebx would mean we exit(1), i.e. with an error status, since ebx still holds 1 from earlier.
    mov     eax,1         ; put __NR_exit into eax
    int     0x80          ;Execute the Linux function

section     .rodata       ; Section for read-only constants

             ;; msg is a label, and in this context doesn't need to be msg:.  It could be on a separate line.
             ;; db = Data Bytes: assemble some literal bytes into the output file.
msg     db  'Hello, world!',0xa     ; ASCII string constant plus a newline (0x10)

             ;;  No terminating zero byte is needed, because we're using write(), which takes a buffer + length instead of an implicit-length string.
             ;; To make this a C string that we could pass to puts or strlen, we'd need a terminating 0 byte. (e.g. "...", 0x10, 0)

len     equ $ - msg       ; Define an assemble-time constant (not stored by itself in the output file, but will appear as an immediate operand in insns that use it)
                          ; Calculate len = string length.  subtract the address of the start
                          ; of the string from the current position ($)
  ;; equivalently, we could have put a str_end: label after the string and done   len equ str_end - str

请注意,我们不会将字符串长度存储在数据内存的任何地方。它是一个汇编时间常数,因此将其作为立即数操作数比加载更有效。我们也可以用三条push imm32指令将字符串数据推送到堆栈上,但代码大小膨胀太多并不是一件好事。


在 Linux 上,您可以将此文件另存为,Hello.asm使用以下命令从中构建 32 位可执行文件

nasm -felf32 Hello.asm                  # assemble as 32-bit code.  Add -Worphan-labels -g -Fdwarf  for debug symbols and warnings
gcc -static -nostdlib -m32 Hello.o -o Hello     # link without CRT startup code or libc, making a static binary

有关将汇编构建为 32 位或 64 位静态或动态链接的 Linux 可执行文件的更多详细信息,请参阅此答案as,以获取 NASM/YASM 语法或带有 GNU指令的 GNU AT&T 语法。 (关键点:在 64 位主机上构建 32 位代码时,请确保使用-m32或等效,否则运行时会遇到令人困惑的问题。)


你可以通过以下方式跟踪它的执行情况,strace以查看它所进行的系统调用

$ strace ./Hello 
execve("./Hello", ["./Hello"], [/* 72 vars */]) = 0
[ Process PID=4019 runs in 32 bit mode. ]
write(1, "Hello, world!
", 14Hello, world!
)         = 14
_exit(0)                                = ?
+++ exited with 0 +++

将其与动态链接进程的跟踪(例如 gcc 从 hello.c 或正在运行的strace /bin/ls)进行比较,以了解动态链接和 C 库启动背后发生了多少事情。

stderr 上的跟踪和 stdout 上的常规输出都在此处进入终端,因此它们会干扰系统调用的线路write。如果您愿意,可以重定向或跟踪到文件。请注意,这让我们可以轻松查看系统调用的返回值,而无需添加代码来打印它们,实际上甚至比使用常规调试器(如 gdb)单步执行并查看这一点更容易。请参阅x86 标签 wikieax的底部以获取 gdb asm 提示。(标签 wiki 的其余部分充满了指向优质资源的链接。)

该程序的 x86-64 版本非常相似,将相同的参数传递给相同的系统调用,只是在不同的寄存器中,并使用syscall而不是。 请参阅如果在 64 位代码中使用 32 位 int 0x80 Linux ABI 会发生什么?int 0x80的底部,获取在 64 位代码中编写字符串和退出的工作示例。


相关:关于为 Linux 创建真正小型 ELF 可执行文件的快速教程。您可以运行的最小二进制文件仅进行 exit() 系统调用。这是为了最小化二进制文件的大小,而不是源代码的大小,甚至只是实际运行的指令数量。


我最初为SO Docs (主题 ID:1164,示例 ID:19078)编写了此代码(第一句除外),重写了 @runner 的一个注释较少的基本示例。这看起来比我之前在 SO docs 实验结束后将其移至另一个问题的答案中 更适合放置它。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   609  
  在现代项目管理中,资源的有效利用是确保项目成功的关键因素之一。随着技术的不断进步,越来越多的工具和软件被开发出来,以帮助项目经理和团队更高效地管理资源。本文将介绍10款工具,这些工具可以帮助项目团队提升资源利用效率,从而实现项目目标。禅道项目管理软件禅道项目管理软件是一款开源的项目管理工具,广泛应用于软件开发和其他行业...
项目管理系统   3  
  在项目管理领域,软件工具的不断升级和创新是推动效率和协作的关键。2024年,众多项目管理软件将迎来一系列令人期待的升级功能,这些新特性不仅将提升团队的工作效率,还将增强用户体验和数据分析能力。本文将详细介绍10款项目管理软件的最新升级功能,帮助项目经理和团队成员更好地规划和执行项目。禅道项目管理软件禅道项目管理软件一直...
开源项目管理工具   2  
  信创国产系统的10个关键厂商及其技术生态随着全球信息技术格局的不断演变,信创(信息技术应用创新)产业作为国产化替代的重要阶段,正逐步成为推动我国信息技术自主可控、安全可靠的核心力量。信创产业不仅关乎国家信息安全,也是数字经济高质量发展的关键支撑。本文将深入探讨信创国产系统中的10个关键厂商及其技术生态,分析它们在信创浪...
项目管理流程   0  
  在探讨项目管理的广阔领域中,成功并非偶然,而是精心策划、高效执行与持续优化的结果。项目管理的成功之道,可以从明确的目标设定与规划、高效的团队协作与沟通、以及灵活的风险管理与适应变化这三个核心方面进行深入解析。每个方面都是项目成功的基石,它们相互交织,共同支撑起项目的顺利推进与最终成就。明确的目标设定与规划项目管理的首要...
建筑工程项目管理规范   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用