使用 64 位 Linux 为 x86_64 编写汇编中的 putchar 吗?
- 2024-10-14 08:40:00
- admin 原创
- 76
问题描述:
我正在尝试使用 writesyscall
来重现putchar
打印单个字符的函数行为。我的代码如下,
asm_putchar:
push rbp
mov rbp, rsp
mov r8, rdi
call:
mov rax, 1
mov rdi, 1
mov rsi, r8
mov rdx, 1
syscall
return:
mov rsp, rbp
pop rbp
ret
解决方案 1:
从中man 2 write
,你可以看到签名是write
,
ssize_t write(int fd, const void *buf, size_t count);
它需要一个指向内存缓冲区的指针 ( const void *buf
)。您不能通过char
值传递它,因此您必须将其存储到内存中并传递一个指针。
write
(除非只有一个字符需要打印,否则不要一次打印一个字符,这确实效率低下,这就是 C stdio 通常会缓冲 I/O 的原因。在内存中构造一个缓冲区并打印:例如这个 x86-64 Linux NASM 函数:如何在没有 c 库中的 printf 的情况下在汇编级编程中打印一个整数?(itoa,整数到十进制 ASCII 字符串))
GCC的 NASM 版本:内联汇编中的 putchar(char),对代码大小/效率进行了一些调整。
; x86-64 System V calling convention: input = byte in DIL
; clobbers: RDI, RSI, RDX, RCX, R11 (last 2 by syscall itself)
; returns: RAX = write return value: 1 for success, -1..-4095 for error
writechar:
lea rsi, [rsp-4] ; RSI = buf in the red zone (below RSP)
mov [rsi], edi ; store the char from RDI into it
mov eax, 1 ; __NR_write syscall number from unistd_64.h
mov edi, 1 ; EDI = fd=1 = stdout
; RSI = buf set earlier, before overwriting the char in EDI
mov edx, eax ; RDX = len = 1 happens to be the same as fd and call #
syscall ; rax = write(1, buf, 1)
ret
我们实际上只需要 1 字节存储,例如mov [rsp-1], dil
,但 4 字节存储可节省一个字节的代码大小。这int putchar(int)
意味着调用者应该写入完整的寄存器,因此即使在旧 CPU 上我们也不会出现部分寄存器停顿。
如果您在 RSI 中传递了无效指针(例如'2'
(integer 50
)),则系统调用将在 RAX 中返回-EFAULT
( -14
)。(内核会将错误代码返回到系统调用中的错误指针,而不是像在用户空间中取消引用时那样传递 SIGSEGV)。
另请参阅汇编中系统调用的返回值是什么?
在玩具程序/实验中,您无需编写代码来检查返回值,而只需在 下运行它们即可strace ./a.out
。如果您编写的程序_start
不使用 libc,则在启动期间不会有任何其他您自己未执行的系统调用,因此读取输出非常容易,否则在您的代码之前会有一堆由 libc 执行的启动系统调用。strace 应该如何使用?
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件