我已经知道如何打印字符串,比如“hello, world”。
我正在Linux上开发。
;
; assemble and link with:
; nasm -f elf printf-test.asm && gcc -m32 -o printf-test printf-test.o
;
section .text
global main
extern printf
main:
mov eax, 0xDEADBEEF
push eax
push message
call printf
add esp, 8
ret
message db "Register = %08X", 10, 0
请注意,printf
使用 cdecl 调用约定,因此我们需要在函数调用后还原堆栈指针,即每传递一个参数需要添加 4 字节。
call printf
而不是call printf@plt
,并且使用绝对地址作为立即数,而不是位置无关的,则可能需要gcc -m32 -no-pie
,或者至少这是一个好主意。但在实践中,对于32位代码,通常可以轻松解决。 - Peter Cordes您需要将它转换为字符串;如果您要处理十六进制数字,那非常容易。任何数字都可以用这种方式表示:
0xa31f = 0xf * 16^0 + 0x1 * 16^1 + 3 * 16^2 + 0xa * 16^3
所以当你有这个数字时,你需要像我展示的那样将其分割,然后将每个“部分”转换为其ASCII等效项。
通过一些位运算很容易得到四个部分,特别是通过右移来将我们感兴趣的部分移动到前四位,然后将结果与0xf进行AND运算以将其与其他部分隔离开来。这就是我的意思(假设我们想要取第3部分):
0xa31f -> shift right by 8 = 0x00a3 -> AND with 0xf = 0x0003
mov si, ??? ; si points to the target buffer
mov ax, 0a31fh ; ax contains the number we want to convert
mov bx, ax ; store a copy in bx
xor dx, dx ; dx will contain the result
mov cx, 3 ; cx's our counter
convert_loop:
mov ax, bx ; load the number into ax
and ax, 0fh ; we want the first 4 bits
cmp ax, 9h ; check what we should add
ja greater_than_9
add ax, 30h ; 0x30 ('0')
jmp converted
greater_than_9:
add ax, 61h ; or 0x61 ('a')
converted:
xchg al, ah ; put a null terminator after it
mov [si], ax ; (will be overwritten unless this
inc si ; is the last one)
shr bx, 4 ; get the next part
dec cx ; one less to do
jnz convert_loop
sub di, 4 ; di still points to the target buffer
PS: 我知道这是16位代码,但我仍然使用旧的TASM :P
PPS: 这是Intel语法,转换为AT&T语法并不难,可以看看这里。
printf(...)
需要)。 - Andrei Bârsansyscall
写入到标准输出。 - Peter Cordes使用printf的Linux x86-64
main.asm
default rel ; make [rel format] the default, you always want this.
extern printf, exit ; NASM requires declarations of external symbols, unlike GAS
section .rodata
format db "%#x", 10, 0 ; C 0-terminated string: "%#x\n"
section .text
global main
main:
sub rsp, 8 ; re-align the stack to 16 before calling another function
; Call printf.
mov esi, 0x12345678 ; "%x" takes a 32-bit unsigned int
lea rdi, [rel format]
xor eax, eax ; AL=0 no FP args in XMM regs
call printf
; Return from main.
xor eax, eax
add rsp, 8
ret
GitHub 上游。
然后:
nasm -f elf64 -o main.o main.asm
gcc -no-pie -o main.out main.o
./main.out
输出:
0x12345678
注意:
sub rsp, 8
:如何使用printf编写64位Mac OS X汇编语言hello world程序?xor eax,eax
:调用printf之前为什么要将%eax清零?-no-pie
:在PIE可执行文件(-pie
)中,普通的call printf
不起作用,链接器仅自动为旧式可执行文件生成PLT存根。你有两个选择:
call printf wrt ..plt
像传统的call printf
一样通过PLT调用
call [rel printf wrt ..got]
完全不使用PLT,就像gcc -fno-plt
一样。
像GAS语法call *printf@GOTPCREL(%rip)
。
这些选项在非PIE可执行文件中也可以使用,除非您正在静态链接libc。在这种情况下,call printf
可以直接解析为call rel32
到libc,因为从您的代码到libc函数的偏移量会在静态链接时已知。
如果要使用C库以外的十六进制数:使用汇编打印十六进制数
在Ubuntu 18.10,NASM 2.13.03上测试通过。
mov
将静态地址放入寄存器中。除非您正在优化位置相关代码,可以使用mov r32,imm32
,否则请使用RIP相对LEA。 - Peter Cordescall printf wrt ..plt
。现在我把它放回到一个更合适的位置,因为你为它列了一个项目符号。我不得不查找NASM等效于GAS call *printf@GOTPCREL(%rip)
的代码,用于没有PLT风格的代码,通过动态符号的早期绑定而不是通过PLT进行惰性链接。(但是有一个好处,就是只需要间接调用,而不是使用PLT进行惰性动态链接的调用+jmp
) - Peter Cordes.plt
是节名称,我猜里面可能有一个额外的.
与缩写一起使用? - Peter Cordescall [rel printf wrt ..got]
和call printf wrt ..plt
的详细信息。我刚刚更新了它,使其适用于NASM和YASM。 - Peter Cordes因为您没有提到数字表示,所以我编写了以下代码,用于任意基数(当然不要太大)的无符号数字,因此您可以使用它:
BITS 32
global _start
section .text
_start:
mov eax, 762002099 ; unsigned number to print
mov ebx, 36 ; base to represent the number, do not set it too big
call print
;exit
mov eax, 1
xor ebx, ebx
int 0x80
print:
mov ecx, esp
sub esp, 36 ; reserve space for the number string, for base-2 it takes 33 bytes with new line, aligned by 4 bytes it takes 36 bytes.
mov edi, 1
dec ecx
mov [ecx], byte 10
print_loop:
xor edx, edx
div ebx
cmp dl, 9 ; if reminder>9 go to use_letter
jg use_letter
add dl, '0'
jmp after_use_letter
use_letter:
add dl, 'W' ; letters from 'a' to ... in ascii code
after_use_letter:
dec ecx
inc edi
mov [ecx],dl
test eax, eax
jnz print_loop
; system call to print, ecx is a pointer on the string
mov eax, 4 ; system call number (sys_write)
mov ebx, 1 ; file descriptor (stdout)
mov edx, edi ; length of the string
int 0x80
add esp, 36 ; release space for the number string
ret
这段代码没有针对以2的幂次方为基数的数字进行优化,也没有使用libc
中的printf
函数。
print
函数会在输出数字后加上一个换行符。数字字符串是在堆栈上创建的。请使用nasm编译代码。
输出:
clockz
xor edx,edx
/ div
或cdq
/ idiv
,以便被除数的零扩展或符号扩展与除法的有符号性匹配。在这种情况下,您需要xor
/div
,以便始终具有正余数。如果您想将输入视为有符号的,则需要测试/js并打印无符号绝对值(如果需要,带有前导“-”)。 - Peter Cordesidiv
改为 div
,这样它才能适用于完整的无符号数范围。嗯,实际上这可能是安全的,因为 2^32-1 / 10 不会溢出 EAX。通过零扩展到 edx:eax,可以得到一个来自 0..2^32-1 的有符号非负被除数。 - Peter Cordesidiv
已经被替换了。我还为数字添加了一个基数。您对此有什么看法?另外,我在堆栈上预留了一个大小为 32 的数字字符串缓冲区。 - TigerTV.ruadd esp, 32
应该改为 sub
来保留空间。你正在占用调用者的堆栈空间。mov byte [ecx], 10
比先设置一个寄存器更有效率。甚至可以使用 push 10
/ mov ecx, esp
/ sub esp, 32
。 (对于当前版本,基数为2的大数字将使用32个数字,但你会用一个换行符占用其中的一个。) - Peter Cordes我对汇编语言相对较新,这显然不是最好的解决方案,但它能够工作。主要函数是_iprint,它首先检查eax中的数字是否为负数,如果是,则打印一个减号,然后通过调用每个数字的函数_dprint来打印单个数字。思路如下:如果我们有512,则等于:512 =(5 * 10 + 1)* 10 + 2 = Q * 10 + R,因此我们可以通过将其除以10并获取余数R来找到数字的最后一位,但如果我们在循环中执行此操作,则数字将以相反的顺序排列,因此我们使用堆栈将它们推入,并在将它们写入stdout时以正确的顺序弹出。
; Build : nasm -f elf -o baz.o baz.asm
; ld -m elf_i386 -o baz baz.o
section .bss
c: resb 1 ; character buffer
section .data
section .text
; writes an ascii character from eax to stdout
_cprint:
pushad ; push registers
mov [c], eax ; store ascii value at c
mov eax, 0x04 ; sys_write
mov ebx, 1 ; stdout
mov ecx, c ; copy c to ecx
mov edx, 1 ; one character
int 0x80 ; syscall
popad ; pop registers
ret ; bye
; writes a digit stored in eax to stdout
_dprint:
pushad ; push registers
add eax, '0' ; get digit's ascii code
mov [c], eax ; store it at c
mov eax, 0x04 ; sys_write
mov ebx, 1 ; stdout
mov ecx, c ; pass the address of c to ecx
mov edx, 1 ; one character
int 0x80 ; syscall
popad ; pop registers
ret ; bye
; now lets try to write a function which will write an integer
; number stored in eax in decimal at stdout
_iprint:
pushad ; push registers
cmp eax, 0 ; check if eax is negative
jge Pos ; if not proceed in the usual manner
push eax ; store eax
mov eax, '-' ; print minus sign
call _cprint ; call character printing function
pop eax ; restore eax
neg eax ; make eax positive
Pos:
mov ebx, 10 ; base
mov ecx, 1 ; number of digits counter
Cycle1:
mov edx, 0 ; set edx to zero before dividing otherwise the
; program gives an error: SIGFPE arithmetic exception
div ebx ; divide eax with ebx now eax holds the
; quotent and edx the reminder
push edx ; digits we have to write are in reverse order
cmp eax, 0 ; exit loop condition
jz EndLoop1 ; we are done
inc ecx ; increment number of digits counter
jmp Cycle1 ; loop back
EndLoop1:
; write the integer digits by poping them out from the stack
Cycle2:
pop eax ; pop up the digits we have stored
call _dprint ; and print them to stdout
dec ecx ; decrement number of digits counter
jz EndLoop2 ; if it's zero we are done
jmp Cycle2 ; loop back
EndLoop2:
popad ; pop registers
ret ; bye
global _start
_start:
nop ; gdb break point
mov eax, -345 ;
call _iprint ;
mov eax, 0x01 ; sys_exit
mov ebx, 0 ; error code
int 0x80 ; край
sys_write()
(以及数字计数)。这比为每个字节进行单独的系统调用要高效得多,并且实际上不需要更多的代码。很容易分配足够长的缓冲区来容纳可能的最长数字字符串,并从末尾开始,因为您知道2^32有多少十进制位数。 - Peter Cordes.toascii_digit:
循环。当然,这是为了优化大小,因此它使用缓慢的div
而不是乘法技巧。 - Peter Cordes
write
系统调用打印,不使用printf
或任何其他函数,包含注释和说明。(译文):本篇讨论如何在堆栈上将整数转换为ASCII十进制字符串,并使用Linuxwrite
系统调用打印,而不使用printf
或其他函数。其中还包括注释和说明。 - Peter Cordes