我一直在研究this nasm code,这是一个玩具程序,可以将命令行参数中的两个数字相加并打印结果。
section .data
SYS_WRITE equ 1
STD_OUT equ 1
SYS_EXIT equ 60
EXIT_CODE equ 0
NEW_LINE db 0xa
WRONG_ARGC db "Must be two command line argument", 0xa
section .text
global _start
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Syscall args
;;
; As we can read in System V AMD64 ABI, the first six function arguments passed in registers. They are:
; rdi - first argument
; rsi - second argument
; rdx - third argument
; rcx - fourth argument
; r8 - fifth argument
; r9 - sixth
; Next arguments will be passed in stack. So if we have function like this:
; int foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7)
; {
; return (a1 + a2 - a3 - a4 + a5 - a6) * a7;
; }
; Then first six arguments will be passed in registers, but 7 argument will be passed in stack.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Commandline args
;;
; If we run application with command line arguments, all of their will be in stack after running in following order:
; [rsp] - top of stack will contain arguments count.
; [rsp + 8] - will contain argv[0]
; [rsp + 16] - will contain argv[1]
; and so on...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_start:
pop rcx ; first thing on the stack is n_args
cmp rcx, 3 ; make sure that n_arg == 3
jne argcError
add rsp, 8 ; increment the stack pointer by a byte, skip argv[0], which is the program name
pop rsi ; get argv[1] address
call str_to_int ; convert argv[1] to int
mov r10, rax ; store the int in r10
pop rsi ; get argv[2] address
call str_to_int ; convert argv[2] to int
mov r11, rax ; store the int in r11
add r10, r11 ; sum up
mov rax, r10 ; store sum in rax
xor r12, r12 ; set r12 to 0
jmp int_to_str
argcError:
;; sys_write syscall
mov rax, SYS_WRITE
;; file descritor, standard output
mov rdi, STD_OUT
;; message address
mov rsi, WRONG_ARGC
;; length of message
mov rdx, 34
;; call write syscall
syscall
;; exit from program
jmp exit
str_to_int:
xor rax, rax; ; set rax to 0
mov rcx, 10
; A loop for reading argc. For example if rsi points to ‘5’ ‘7’ ‘6’ ‘\000’ sequence, then will be following steps:
; rax = 0
; get first byte - 5 and put it to rbx
; rax * 10 --> rax = 0 * 10
; rax = rax + rbx = 0 + 5
; Get second byte - 7 and put it to rbx
; rax * 10 --> rax = 5 * 10 = 50
; rax = rax + rbx = 50 + 7 = 57
; and loop it while rsi is not \000
next:
cmp [rsi], byte 0 ; check if argv[1] is null byte, if yes, return
je return_str
mov bl, [rsi] ; copy argv[1] into register bl
sub bl, 48 ; '0' to '9' in ascii is 48 to 57, so we need to subtract by 48
mul rcx
add rax, rbx
inc rsi
jmp next
return_str:
ret
;;
;; Print number
;;
print:
;;;; calculate number length
mov rax, 1
mul r12 ; number of chars
mov r12, 8 ; why? 8 bits each char?
mul r12
mov rdx, rax
;;;;
;;;; print sum
mov rax, SYS_WRITE
mov rdi, STD_OUT
mov rsi, rsp
;; call sys_write
syscall
;;
;; newline
jmp printNewline
;;
;; Print number
;;
printNewline:
mov rax, SYS_WRITE
mov rdi, STD_OUT
mov rsi, NEW_LINE
mov rdx, 1
syscall
jmp exit
;;
;; Convert int to string
;;
int_to_str:
;; reminder from division
mov rdx, 0
;; base
mov rbx, 10
;; rax = rax / 10
div rbx
;; add \0
add rdx, 48
;; push reminder to stack
push rdx
;; increment number of chars
inc r12
;; check factor with 0
cmp rax, 0x0
;; loop again
jne int_to_str
;; print result
jmp print
;;
;; Exit from program
;;
exit:
;; syscall number
mov rax, SYS_EXIT
;; exit code
mov rdi, EXIT_CODE
;; call sys_exit
syscall
大多数代码简单明了,但是我一直在努力计算要打印的字符数。在我看来,L154已经处理完了计数,为什么在L112中需要再次将其乘以8?有人可以解释一下吗?
谢谢。