Linux/Golang/glibC系统调用

Linux/Golang/glibC系统调用

本文主要通过分析Linux环境下Golang的系统调用,以此阐明整个流程

有时候涉略过多,反而遭到质疑~,写点文章证明自己实力也好

Golang系统调用

找个函数来分析
https://pkg.go.dev/os/exec#Cmd.Wait

源码文件在src/os目录下的: exec.go -> exec_unix.go -> pidfd_linux.go
https://github.com/golang/go/blob/2f6426834c150c37cdb1330b48e9903963d4329c/src/os/exec.go#L134
go/src/os
/exec.go

go/src/os
/exec_unix.go

go/src/os
/pidfd_linux.go

往下是系统调用: src/syscall目录的 syscall_linux.go -> ``
go/src/syscall
/syscall_linux.go

image

runtime层的:src/internal/runtime/syscall/syscall_linux.go,如下图,可以看见Sysacll6只有声明没有函数体,是个外部声明。
src/internal/runtime/syscall/syscall_linux.go

其函数体内容实际上位于同目录下的 .s 汇编文件,与编译时采用的架构工具链相关。

  • 如编译工具链采用amd64则会编译链接位于 src/internal/runtime/syscall/asm_linux_amd64.s 的汇编文件
    image
  • 如编译工具链采用arm64,则会编译链接 src/internal/runtime/syscall/asm_linux_arm64.s 的汇编文件
    image

by the way: 这里的语法是Golang汇编,属于Plan9分支。
golang汇编参考资料:

  1. 官网资料 https://go.dev/doc/asm
  2. 简洁概述 https://hopehook.com/post/golang_assembly/

总结:Golang直接了当地使用汇编实现了系统调用(软中断号),而不需要再通过 libc 去调用系统调用库。这样的好处是不需要考虑 glibc 繁杂沉重的兼容性方案。

Linux 定义的系统调用表

  • 很全的表格:https://gpages.juszkiewicz.com.pl/syscalls-table/syscalls.html
  • ARM64架构:
  • x86_32位架构tbl系统调用表: https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_32.tbl
  • x86_64位架构tbl系统调用表: https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl

本地审计工具:ausyscall --dump

Linux系统调用

内核实现

通过软中断陷入内核态/特权模式
和STM32 ARM核心一样,都是由一个异常向量表描述中断对应的Handler地址,软硬中断也是一样。
系统调用函数在 include/linux/syscalls.h中定义

我们拿asmlinkage long sys_openat(int dfd, const char __user *filename, int flags, umode_t mode); 来分析

这里使用了汇编链接,它和上文提到的tbl系统调用表有关。我们拿x86/i386分析,arch/x86/entry/syscalls/syscall_32.tbl
32.tbl
中断号 295 架构i386即传统32位x86 sys_openat 是其回调函数/软中断Handler

linux/include/uapi/asm-generic/unistd.h

其实现位于 arch/处理器架构/include/之下
可以在 arch/x86 下搜索 openat

关于内核的系统调用这部分,本人会在再出一个文章。

Glibc 系统调用库

注意:Glibc属于库,不属于内核,是根文件系统的一部分。
我们在应用态陷入内核态,使用的c库里的open()等等函数,最后都是链式调用到了syscall()类的系统调用函数。

看glibc的源码,就会发现弯弯绕绕,最后是调用到

  • x86汇编 https://github.com/bminor/glibc/tree/master/sysdeps/unix/x86_64

  • arm汇编 https://github.com/bminor/glibc/tree/master/sysdeps/unix/arm

    • sysdeps/unix/arm/sysdep.S
      image

    • sysdeps/unix/arm/sysdep.h
      image

作用是将参数写入寄存器,让SoC自己触发软中断,根据Linux内核注册的软中断号执行对应地址段的函数,也就是我们常在STM32里注册定义的中断的handle函数。

Linux应用态到内核态例子

在线阅读代码:

  1. https://elixir.bootlin.com/glibc/glibc-2.29/source/include/errno.h#L37
  2. https://codebrowser.dev/glibc/glibc/io/read.c.html
  3. 带了编译产物的仓库 https://github.com/bminor/glibc/tree/a81cdde1cb9d514fc8f014ddf21771c96ff2c182
    这些在线网站都不错,但为了高亮,所以我截图放了github的

我们在应用层调用 系统库的 fread()函数
其链接到glibc库的 libio/iofread.c
image

其中第44行可见其为 _IO_fread 声明了weak弱链接别名 fread,有关别名表可见编译产物如sysdeps/unix/syscalls.list
做了一些预操作之后,调用libio/libio.h 声明的 libio/genops.c:_IO_sgetn
image
宏定义 libio/libioP.h
image
ps: JUMP2代表两个参数

image
展开宏
image
展开宏
image

展开宏

image
展开宏
image

结构体
image

也就说调用了 FP.__xsgetn(FP, DATA, N) ,展开差不多是

struct _IO_FILE_plus *THIS;
THIS->vtable->__xsgetn; 即_IO_xsgetn_t类型函数指针

即THIS/file对象的函数地址 size_t __xsgetn (FILE *FP, void *DATA, size_t N);

初始化位于
https://codebrowser.dev/glibc/glibc/libio/tst-vtables-common.c.html#jumps

相关的计数器
image

再看另一个,我们常用的fopen
fopen

_IO_file_fopen

compat_symbol (libc, _IO_old_file_fopen, _IO_file_fopen, GLIBC_2_0);

_IO_old_file_fopen

#define __open open

这里定向到了 open, 我们需要通过编译产物 sysdeps/unix/syscalls.list 找到其链接段
image

可在 io/open.c 找到函数 __libc_open 位于
image
由于弱定义,所以被以下覆盖 sysdeps/unix/sysv/linux/open.c

image
关键在于第43行的SYSCALL_CANCEL,其中的宏
image
展开宏INLINE_SYSCALL_CALL
image
展开宏
image
展开宏
image
展开宏
image

展开为

//展开
__INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode)
//继续展开为
__INLINE_SYSCALL(openat, 4, AT_FDCWD, file, oflag, mode)

image

  • ARM64位于 sysdeps/unix/sysv/linux/aarch64/sysdep.h
  • X86的位于 sysdeps/unix/sysv/linux/x86_64/sysdep.h
//x86
#define SYS_ify(syscall_name)	__NR_##syscall_name#define INTERNAL_SYSCALL(name, nr, args...)				\internal_syscall##nr (SYS_ify (name), args)
//aarch64即 arm64
# define INTERNAL_SYSCALL_AARCH64(name, nr, args...)		\INTERNAL_SYSCALL_RAW(__ARM_NR_##name, nr, args)

x86的展开为 internal_syscall4 (__NR_openat, AT_FDCWD, file, oflag, mode)
可在 sysdeps/unix/sysv/linux/sh/arch-syscall.h 找到,其中断号为 295
image

对应openat的x86/i386中断号,刚好就是295,源码分析完全正确!每种平台的中断号都不一样,但是这样分析是正确的。
体验一下GNU宏地狱吧!

而ARM64的就高明得多,直接通过asm汇编指令写寄存器跳转执行__libc_do_syscall完成
image
glibc/sysdeps/unix/sysv/linux/arm/libc-do-syscall.S
image

总之是一个系统调用,等价于 openat(AT_FDCWD, file, oflag, mode);

总结:sysdeps是系统调用的实现,向上屏蔽细节,但是封装的过程用于一堆条件宏,根本没办法用代码分析工具,也难以调试。
对GNU LIBC代码的个人拙见:
好处:节省空间,较好的运行速度。
坏处:作为计算机世界的底层支持,这样还不如在编译器优化阶段下功夫,过多的黑魔法必然写出难以理解的代码,牵一发动全身,没有人愿意去改这堆疯狂嵌套的代码。作为新兴语言爱好者的我,始终认为程序要少点黑魔法,简洁直接才是最优解,剩下的东西都应该交给编译器,何况系统调用的耗时从来就不在这里,主要性能影响都是在内核态用户态切换的时候,并不在c库本身。

GNU的代码向来很难读,glibc更是个寄吧,各种宏和硬链接乱飞,有再好的代码阅读工具也难找出来。
但要说来,说到底还是c语言/链接器的设计缺陷,没办法更好的实现动态表和静态表。(多态组合、编译期间的函数静态段的多分支链接)

微软的代码是框架难以理解,因为他们也不给出架构图和代码结构的...,,而GNU的代码是宏和链接难以理解。

最后

请指正批评!感谢阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/708318.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[转帖]【全网首发】一些可以显著提高 Java 启动速度方法原创

https://heapdump.cn/article/4136322?from=pc 我们线上的业务 jar 包基本上普遍比较庞大,动不动一个 jar 包上百 M,启动时间在分钟级,拖慢了我们在故障时快速扩容的响应。于是做了一些分析,看看 Java 程序启动慢到底慢在哪里,如何去优化,目前的效果是大部分大型应用启动…

博客园美化:canvas炫酷背景

话不多说,先上效果图:yysy,这个当背景确实酷炫!!!😄 页脚HTML代码:1 <script src="https://files.cnblogs.com/files/zhonglinke/vendors.js"></script>2 <script type="text/javascript" language="javascript">3 4…

APC挂靠

5.APC挂靠 用户态apc 和上一课的内核apc几乎一致,唯一的变动就是这个 //插入当前线程KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, 0x4011d0, UserMode, NULL);改成了UserMode函数地址改成了进程的地址0x4011d0 完整代码 Driver-mai…

Java(7)-Maven抽取公共模块构建jar包

前提假设:项目中有两个Moudle,分别是Moudle1和Moudle2,它们有些代码是相同的,比如相同的类和API,重复书写当然可以正常运行,但是我们可以用maven打包成jar包,其他Moudle直接引用即可。 步骤 1.新建一个Module-common pox.xml 中配置 Module1 和 Moudle2 同样使用的依赖:…

KPCR进程概念

1.KPCR进程概念 KPCR 介绍 KPCR 是CPU的控制结构 FS段寄存器在R0(FS=0x30)的时候指向KPCR结构 FS 段寄存器在R3(FS=0x3b)的时候指向 当前线程的TEB(线程) 线程结构是运行在CPU上面,所以线程结构是放在CPU上的 kd> dt _KPCR ntdll!_KPCR+0x000 NtTib : _NT_TIB…

驱动断链

03驱动断链获取驱动信息的途径是从Driver和FileSystem两个目录下获得的正常情况下我们自己做驱动遍历只能便利Driver下的所有驱动,也就是驱动链表里的驱动 分析 这里我们F12进去找一下DRIVER_OBJECT的结构 typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT; WinDbg里dt一下 kd&…

蓝屏分析

04蓝屏分析 直接上蓝屏代码 #include <ntifs.h>VOID Unload(PDRIVER_OBJECT pDriver) {DbgPrint("Driver Unload\r\n"); }NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {pDriver->DriverUnload = Unload;//DbgBreakPoint();DbgPrin…

Qt学习第二篇(基本小组件的使用)

Qt_2 小部件是 GUI 的基本元素。 它也称为UI 控件。 它接受来自底层平台的不同用户事件,如鼠标和键盘事件(以及其他事件)。 我们使用不同的小部件创建 UI。 曾经有一段时间,所有的 GUI 控件都是从头开始编写的。 Qt 小部件通过开发具有现成的 GUI 控件的桌面 GUI 来缩短时间,…

Weblogic T3反序列化漏洞(CVE-2018-2628)

在Weblogic中RMI通信的实现是使用T3协议,并且在T3的传输过程中,和RMI一样,会进行序列化和反序列化的操作。目录前言T3协议概述漏洞复现修复方案 前言 WebLogic Server 是一个企业级的应用服务器,由Oracle公司开发,支持完整的Java EE规范,包括EJB、JSP、Servlet、JMS等,适…

方方方的数据结构

总算给我看懂到底是什么意思了。。。 首先我们来考虑按照时间+扫描线进行处理,假设操作如下黑色是加操作,黄色是乘操作,绿色是加操作,对于红色那条线所代表的点,随着时间的流逝,首先在刚刚进入黑色的时候,这一点的值就被加上了一个数,然后刚刚进入黄色的时候,这一点的…

手机硬件检测:-DeviceTest

手机硬件检测:Z-DeviceTest官方版是款针对手机硬件所打造的检测工具。手机硬件检测:Z-DeviceTest能够检测硬件和OS,硬件上不仅仅是电池、cpu、内存、OS,甚至连usb、扬声器、指南针、摄像头、GPS、听筒等都能检测。并且手机硬件检测:Z-DeviceTest还能够对市面众多的机型进行检…

OutOfMemoryError

以下的这段代码应该是报错的才对,但是我在运行了之后,程序一直卡在那里。最后请教老师了解到,原来jvm如果不指定运行参数,是会进行自动扩容的。 package com.coding.jvm.oom;public class NativeErrorDemo {public static void main(String[] args) {for (; ; ) {new Threa…