在64位Linux上使用中断0x80

7
我有一个简单的64位汇编程序,旨在打印一个'O'和'K',然后换行。
然而,'K'从未被打印。程序的目标之一是将rax寄存器中较低位的值作为ASCII字符打印出来。该程序专门为64位Linux编写,用于教育目的,因此无需使用C风格的系统调用。
我怀疑问题要么出在`mov QWORD [rsp], rax`,要么出在`mov rcx, rsp`。
目前,该程序仅输出'O'后跟一个换行符。
如何更改程序以使用rax中的值,然后打印一个'K',使完整输出为'OK'后跟一个换行符?
bits 64

section .data

o:  db "O"      ; 'O'
nl: dq 10       ; newline

section .text

;--- function main ---
global main         ; make label available to the linker
global _start       ; make label available to the linker
_start:             ; starting point of the program
main:               ; name of the function

;--- call interrupt 0x80 ---
mov rax, 4          ; function call: 4
mov rbx, 1          ; parameter #1 is 1
mov rcx, o          ; parameter #2 is &o
mov rdx, 1          ; parameter #3 is length of string
int 0x80            ; perform the call

;--- rax = 'K' ---
mov rax, 75         ; rax = 75

;--- call interrupt 0x80 ---
sub rsp, 8          ; make some space for storing rax on the stack
mov QWORD [rsp], rax        ; move rax to a memory location on the stack
mov rax, 4          ; function call: 4
mov rbx, 1          ; parameter #1 is 1
mov rcx, rsp            ; parameter #2 is rsp
mov rdx, 1          ; parameter #3 is length of string
int 0x80            ; perform the call
add rsp, 8          ; move the stack pointer back

;--- call interrupt 0x80 ---
mov rax, 4          ; function call: 4
mov rbx, 1          ; parameter #1 is 1
mov rcx, nl         ; parameter #2 is nl
mov rdx, 1          ; parameter #3 is length of string
int 0x80            ; perform the call

;--- exit program ---
mov rax, 1          ; function call: 1
xor rbx, rbx            ; return code 0
int 0x80            ; exit program

更新:请注意,这是一个使用int 80h的64位x86汇编程序,与使用int 80h的32位x86汇编程序非常不同。

1
然而,这个问题并不是重复的,因为在这种情况下,使用int 0x80的程序是一个64位程序。 - firo
我可以在ELF 64位LSB程序中使用'int 0x80'。这都是因为内核COMPAT_32选项的存在。我的意思是不要混淆程序和CPU/OS模式。 - firo
1个回答

16

很明显,您编写了一个64位程序并使用“int 0x80”指令。然而,“int 0x80”在32位程序中才能正常使用。

堆栈的地址位于32位程序无法访问的范围内。因此,“int 0x80”类型的系统调用很可能不允许访问这个内存区域。

解决这个问题有两种可能性:

  • 编译为32位应用程序(使用32位寄存器如EAX而不是64位寄存器如RAX)。当您在不使用任何共享库的情况下链接时,32位程序将在64位Linux上完美运行。
  • 使用“syscall”样式的系统调用而不是“int 0x80”样式的系统调用。它们的使用与“int 0x80”样式的系统调用有很大的区别!

32位代码:

mov eax,4    ; In "int 0x80" style 4 means: write
mov ebx,1    ; ... and the first arg. is stored in ebx
mov ecx,esp  ; ... and the second arg. is stored in ecx
mov edx,1    ; ... and the third arg. is stored in edx
int 0x80

64位代码:

mov rax,1    ; In "syscall" style 1 means: write
mov rdi,1    ; ... and the first arg. is stored in rdi (not rbx)
mov rsi,rsp  ; ... and the second arg. is stored in rsi (not rcx)
mov rdx,1    ; ... and the third arg. is stored in rdx
syscall

--- 编辑 ---

背景信息:

"int 0x80" 是为了32位程序而设计的。当从64位程序中调用时,它的行为与从32位程序中调用一样(使用32位调用约定)。

这也意味着 "int 0x80" 的参数将在32位寄存器中传递,并且忽略64位寄存器的高32位。

(我刚刚在 Ubuntu 16.10 64位上测试过。)

然而,这意味着当使用 "int 0x80" 时,您只能访问2^32以下的内存(甚至低于2^31),因为您不能在32位寄存器中传递超过2^32的地址。

如果要写入的数据位于2^31以下的地址,则可以使用 "int 0x80" 写入数据。如果位于2^32以上,则无法写入。堆栈(RSP)很可能位于2^32以上,因此您不能使用 "int 0x80" 在堆栈上写入数据。

因为您的程序很可能使用2^32以上的内存,所以我写道:"int 0x80 不适用于64位程序。"


当使用rax、rdi、rsi和rdx代替eax、ebx、ecx和edx,并将int 0x80更改为syscall时,程序在这里出现段错误,而不仅仅是打印“O”和换行符。尽管如此,32位版本可以正常工作。 - Alexander
2
“int 0x80” 在64位模式下也可以正常工作,但您仍然需要遵循64位调用约定(使用寄存器和系统调用号码)。 - Jester
@jester,我在我的答案中添加了一些“编辑”以使其更清晰。 - Martin Rosenau
1
简而言之,在64位进程中,int 0x80仍会调用32位ABI,完全没有改变。由于它速度较慢且仅支持32位指针(以及任何结构的32位版本),因此它没有用处。它与SYSCALL不等价,甚至不相似,这使得Jester犯了一个罕见的错误。 :P - Peter Cordes
1
int 0x80 同时将 r8-r11 寄存器清零。我写了一个问答,试图规范回答有关在64位模式下使用它时发生的一切,并展示了 int 0x80syscall 在 write() 函数上的示例。https://dev59.com/fFYO5IYBdhLWcg3wRfd- - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接