linux下64位汇编的系统调用(4)

简介:

经过上一篇的铺垫貌似可以很轻松的用汇编写出mmap的代码来,可仔细一看,还是有不少问题需要解决:

1.系统调用mmap如果出错并不直接返回MAP_FAILED(-1),而是一个“类似”值;C库中的mmap函数对其做了包装,使其最终返回-1;如果我们直接调用mmap syscall,则这些事必须自己来做。

2.C库函数如果出错会设置errno的值,而在汇编中没法直接用:

extern errno

的方法使用外部的值,连接时会报错:

/usr/bin/ld: errno: TLS definition in /lib/x86_64-linux-gnu/libc.so.6 section .tbss mismatches non-TLS reference in p.o
/lib/x86_64-linux-gnu/libc.so.6: error adding symbols: 错误的值

C语言的解决办法很简单,直接把:

extern int errno;
//替换为
#include <errno.h>

但nasm下这招没法使;我们先看一下errno对应的C代码:

#define errno() *errnofunc()

int *errnofunc() 
{
    int *errnoptr = get_thread_data(ERRNOPTR);
    return errnoptr;
}

可以看到其调用另一个函数,在nasm中我们可以大致这么写:

extern __errno_location
call __errno_location
mov rax,qword [rax]

不过貌似也不太对 :( ,不过我们可以在mmap系统调用后自己操作errno的值,以下代码将填充变量errno的值并且如果出错将修正mmap的返回值为-1:

section .data
errno dq 0

;mmap syscall 之后
cmp rax,0xfffffffffffff001
jb next
push rax
neg rax
mov [errno],rax
pop rax
or rax,-1
next:
;处理mmap返回后的逻辑

3 可以看到C代码中调用mmap参数压栈,是将第4个参数放到rcx里,但是在C库mmap函数里又将rcx赋值给r10;这正应了前面调用规则里的内核系统调用第4个参数是放在r10里,而不是rcx里的哦;所以汇编中我们直接放在r10里即可,不用先转到rcx里了。

最后的代码如下:

section .data
    errno dq 0
    addr dq 0

MAP_FAILED equ -1
;MAP_LEN equ 40960
MAP_LEN equ 0xffffffffffffffff
PROT_READ_WRITE equ 3
MAP_SHARED_ANON equ 33
;MAP_SHARED_ANON equ 0x20

section .text
;extern errno
extern __errno_location
extern strerror
extern printf
;if use ld
;global _start
;if use gcc
global main
;_start:
main:
    and rsp,~0xffff            ;堆栈对齐 equ 0xffffffffffff0000

    mov rax,9               ;mmap NO
    mov rdi,0               ;map address
    mov rsi,MAP_LEN             ;map size
    mov rdx,PROT_READ_WRITE
    mov r10,MAP_SHARED_ANON
    mov r8,-1               ;忽略fd
    mov r9,0                ;offset
    syscall
    cmp rax,0xfffffffffffff001
    jb next
    push rax
    neg rax
    mov [errno],rax
    pop rax
    or rax,-1
next:
    cmp rax,MAP_FAILED
    ;cmp rax,0
    ;js map_failed
    je map_failed

    mov [addr],rax
    mov rsi,[addr]
    mov rdi,msg_success
    call printf

    mov rax,11
    mov rdi,addr
    mov rsi,MAP_LEN
    syscall
    jmp exit

map_failed: 
    ;mov rdi,0xb
    ;call __errno_location
    ;mov rax,qword [rax]
    ;mov rsi,[errno]

    mov rdi,[errno]
    call strerror
    mov rsi,rax
    mov rdi,msg_failed
    call printf

exit:
    mov rax,60      ;exit NO
    mov rdi,0       ;error_code
    syscall

    msg_success: db "map successed , addr at %p",0ax,0
    msg_failed: db "map failed ,due to %s",0ax,0

编译连接:

nasm -f elf64 p.s
gcc -o p p.o

如果mmap成功结果如下:

wisy@wisy-pad:~/src/asm_src/nasm_src/linux$ ./p
map successed , addr at 0x7fbe5c94e000

可以用strace查看其返回的syscall:

mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0x7fc6b2397000

如果mmap出错则会显示出错原因:

//将MAP_LEN 设为超大的值
./p
map failed ,due to Cannot allocate memory
//传递给flags错误的参数
./p
map failed ,due to Invalid argument
相关文章
|
13天前
|
网络协议 Linux 调度
深入探索Linux操作系统的心脏:内核与系统调用####
本文旨在揭开Linux操作系统中最为核心的部分——内核与系统调用的神秘面纱,通过生动形象的语言和比喻,让读者仿佛踏上了一段奇妙的旅程,从宏观到微观,逐步深入了解这两个关键组件如何协同工作,支撑起整个操作系统的运行。不同于传统的技术解析,本文将以故事化的方式,带领读者领略Linux内核的精妙设计与系统调用的魅力所在,即便是对技术细节不甚了解的读者也能轻松享受这次知识之旅。 ####
|
9天前
|
缓存 算法 安全
深入理解Linux操作系统的心脏:内核与系统调用####
【10月更文挑战第20天】 本文将带你探索Linux操作系统的核心——其强大的内核和高效的系统调用机制。通过深入浅出的解释,我们将揭示这些技术是如何协同工作以支撑起整个系统的运行,同时也会触及一些常见的误解和背后的哲学思想。无论你是开发者、系统管理员还是普通用户,了解这些基础知识都将有助于你更好地利用Linux的强大功能。 ####
18 1
|
3月前
|
项目管理 敏捷开发 开发框架
敏捷与瀑布的对决:解析Xamarin项目管理中如何运用敏捷方法提升开发效率并应对市场变化
【8月更文挑战第31天】在数字化时代,项目管理对软件开发至关重要,尤其是在跨平台框架 Xamarin 中。本文《Xamarin 项目管理:敏捷方法的应用》通过对比传统瀑布方法与敏捷方法,揭示敏捷在 Xamarin 项目中的优势。瀑布方法按线性顺序推进,适用于需求固定的小型项目;而敏捷方法如 Scrum 则强调迭代和增量开发,更适合需求多变、竞争激烈的环境。通过详细分析两种方法在 Xamarin 项目中的实际应用,本文展示了敏捷方法如何提高灵活性、适应性和开发效率,使其成为 Xamarin 项目成功的利器。
49 1
|
3月前
|
Linux
揭秘Linux心脏:那些让你的编程事半功倍的主要系统调用
【8月更文挑战第31天】Linux中的系统调用是操作系统提供给应用程序的接口,用于请求内核服务,如文件操作、进程控制等。本文列举了22种主要系统调用,包括fork()、exec()、exit()、wait()、open()、close()、read()、write()等,并通过示例代码展示了如何使用fork()创建新进程及使用open()、write()、close()操作文件。这些系统调用是Linux中最基本的接口,帮助应用程序与内核交互。
42 1
|
3月前
|
C语言
Linux0.11 系统调用进程创建与执行(九)(下)
Linux0.11 系统调用进程创建与执行(九)
32 1
|
3月前
|
存储 Linux 索引
Linux0.11 系统调用进程创建与执行(九)(上)
Linux0.11 系统调用进程创建与执行(九)
72 1
|
3月前
|
安全 Linux 程序员
在Linux中,系统调用是什么?
在Linux中,系统调用是什么?
|
3月前
|
安全 Linux 程序员
在Linux中,什么是系统调用?举例说明其作用是什么?
在Linux中,什么是系统调用?举例说明其作用是什么?
|
3月前
|
存储 Linux API
Linux源码阅读笔记08-进程调度API系统调用案例分析
Linux源码阅读笔记08-进程调度API系统调用案例分析
|
2月前
|
存储 Linux 程序员
Linux中的主要系统调用
【9月更文挑战第11天】在Linux操作系统中,通过系统调用`fork`创建新进程,子进程继承父进程的数据结构与代码,但可通过`execve`执行不同程序。`fork`返回值区分父子进程,`waitpid`让父进程等待子进程结束。