我正在尝试构建一个将文件读入内存的x86程序。它使用了一些不同的系统调用,以及内存等等。那里有很多要弄清楚。
为了简化调试并弄清楚这一点,我想添加assert
语句,如果不匹配,它会输出一个很好的错误信息。这是学习汇编的第一步,因此我可以打印放在不同寄存器上的数字和字符串等操作后。然后我可以打印出来并调试它们,而无需任何花哨的工具。
想知道是否可以帮我在NASM for Mac x86-64上写一个ASSERT
和PRINT
。到目前为止我有这个:
%define a rdi
%define b rsi
%define c rdx
%define d r10
%define e r8
%define f r9
%define i rax
%define EXIT 0x2000001
%define EXIT_STATUS 0
%define READ 0x2000003 ; read
%define WRITE 0x2000004 ; write
%define OPEN 0x2000005 ; open(path, oflag)
%define CLOSE 0x2000006 ; CLOSE
%define MMAP 0x2000197 ; mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t offset)
%define PROT_NONE 0x00 ; no permissions
%define PROT_READ 0x01 ; pages can be read
%define PROT_WRITE 0x02 ; pages can be written
%define PROT_EXEC 0x04 ; pages can be executed
%define MAP_SHARED 0x0001 ; share changes
%define MAP_PRIVATE 0x0002 ; changes are private
%define MAP_FIXED 0x0010 ; map addr must be exactly as requested
%define MAP_RENAME 0x0020 ; Sun: rename private pages to file
%define MAP_NORESERVE 0x0040 ; Sun: don't reserve needed swap area
%define MAP_INHERIT 0x0080 ; region is retained after exec
%define MAP_NOEXTEND 0x0100 ; for MAP_FILE, don't change file size
%define MAP_HASSEMAPHORE 0x0200 ; region may contain semaphores
;
; Assert equals.
;
%macro ASSERT 3
cmp %1, %2
jne prepare_error
prepare_error:
push %3
jmp throw_error
%endmacro
;
; Print to stdout.
;
%macro PRINT 1
mov c, getLengthOf(%1) ; "rdx" stores the string length
mov b, %1 ; "rsi" stores the byte string to be used
mov a, 1 ; "rdi" tells where to write (stdout file descriptor: 1)
mov i, WRITE ; syscall: write
syscall
%endmacro
;
; Read file into memory.
;
start:
ASSERT PROT_READ, 0x01, "Something wrong with PROT_READ"
mov b, PROT_READ
mov a, PROT_WRITE
xor a, b
mov f, 0
mov e, -1
mov d, MAP_PRIVATE
mov c, a
mov b, 500000
mov a, 0
mov i, MMAP
syscall
PRINT "mmap output "
PRINT i ; check what's returned
PRINT "\n"
mov e, i
mov b, O_RDONLY
mov a, "Makefile"
mov i, OPEN
syscall
mov a, i
mov b, e
mov i, READ
syscall
;
; Exit status
;
exit:
mov a, EXIT_STATUS ; exit status
mov i, EXIT ; syscall: exit
syscall
throw_error:
PRINT pop() ; print error or something
jmp exit
mov rsi, "abcdefgh"
是字符串内容的mov-immediate,而不是指向它的指针。如果你这样做,它只会立即存在。
您的宏需要切换到.rodata
并返回以将字符串放入内存;可能你可以用NASM宏将它变成一个直接推送到堆栈的序列,但听起来很难。
所以你可以使用通常的msglen equ $ - msg
来获得长度。 (实际上使用NASM本地标签,因此宏不会产生冲突)。
请参阅NASM - Macro local label as parameter to another macro,几周前我基本上写了这个答案。但不完全是重复,因为它没有使用字符串作为立即的错误。
无论如何,NASM没有支持AFAIK切换部分,然后回到当前部分,如GAS .pushsection
。所以除非你想为节名添加一个可选参数,否则我们就会硬编码section .text
。
; write(1, string, sizeof(stringarray))
; switches to SECTION .text regardless of previous section
; clobbers: RDI, RSI, RDX, RCX,R11 (by syscall itself)
: output: RAX = bytes written, or -errno
%macro PRINT 1
section .rodata
;; NASM macro-local labels
%%str db %1 ; put the string in read-only memory
%%strln equ $ - %%str ; current position - string start
section .text
mov edx, %%strlen ; len
lea rsi, [rel %%str] ; buf = the string. (RIP-relative for position-independent)
mov edi, 1 ; fd = stdout
mov eax, WRITE
syscall
%endmacro
这不会尝试组合相同字符串的重复项。使用相同的消息多次使用它将是低效的。这与调试无关。
我本可以让你的%定义为RDI,并让NASM将mov rdi, 1
(7字节)优化为mov edi, 1
(5字节)。但YASM不会这样做,所以如果你关心任何人使用YASM构建你的代码,最好明确它。
我使用了RIP相对LEA,因为这是在与位置无关的代码中将静态地址放入寄存器的最有效方法。在Linux非PIE可执行文件中,使用mov esi, %%str
(5个字节,可以在任何端口上运行,超过LEA)。但是在OS X上,映射/加载可执行文件的基本虚拟地址总是高于2 ^ 32,并且您永远不希望mov r64, imm64
具有64位绝对地址。
在Linux上,系统调用号是小整数,你可以使用lea eax, [rdi-1 + WRITE]
用3字节指令执行eax = SYS_write而对于mov执行5。