glibc源码分析(二)系统调用
在glibc源码中,许多系统调用被使用了.c封装的方式进行封装。这一过程借助嵌入式汇编,严格遵循系统调用封装规则。以stat函数为例,其实现揭示了.c封装的根文件挂载源码奥秘。
在源代码中,stat系统调用被INLINE_SYSCALL宏所封装。该宏首先调用INTERNAL_SYSCALL宏,执行系统调用并把返回值存入resultvar变量中。接下来,通过判断系统调用是否成功执行,采取相应的后续操作。若执行错误,则调用__syscall_error设置errno并返回-1;若执行成功,则返回resultvar。
在处理系统调用参数个数nr时,INTERNAL_SYSCALL宏发挥了关键作用。根据nr的不同,宏会调用不同的内部函数进行处理。例如,当nr为0时,调用INTERNAL_SYSCALL_MAIN_0宏,设置eax寄存器为系统调用号,执行*_dl_sysinfo函数进行系统调用。当nr为1时,宏将参数1存入ebx寄存器,同时设置eax寄存器为系统调用号,并执行系统调用。
类似的,nr为2、3、4、5或6时,宏分别会将参数2至6存入ecx、edx、esi、逆风网吧特权源码edi或ebp寄存器中,并与系统调用号相结合,执行*_dl_sysinfo函数。通过这一系列的嵌入式汇编操作,.c文件成功封装了系统调用,实现了高效、精确的调用过程。
总的来说,glibc中.c封装的实现展示了汇编语言的强大功能,以及在系统调用处理中的应用。通过精确的汇编指令和灵活的参数传递,封装过程确保了系统调用的执行效率和正确性。
C++/C 内存分配-malloc/mmap/syscall深度解析以及性能测试
用于实现动态内存分配函数(如malloc、free等)以及操作系统级的内存管理。
通常情况下,malloc和free会使用brk或sbrk来动态管理进程的堆空间。它们会请求增加或减少堆空间的大小,以满足动态内存分配的需求。
在理解brk和sbrk时,需要考虑以下几点:
上面这些都是理论知识,和实际还有不小的差距,大家不要直接记这些理论,一定要动手自己实践,看到什么样的结果,就是什么样,看不到的就后面有机会再补充。
(文章内涉及的源码截图或者片段,若您需要源码工程,可以关注后留言找我要。 )
首先在大多数系统中,栈是有固定大小的,当程序启动时系统会为栈分配一块固定大小的内存空间。栈的大小受限于系统的限制,当栈空间用尽时会引发栈溢出(stack overflow)错误。所以栈不存动态增长的可能,所以我们暂时只分析堆的刷 赞平台源码内存分配。
注意这个KB,说的是每次沈内存的时候判断,不是说累计情况,比如你每次申请1KB,申请了多次,那肯定超了,此时还是会继续使用brk分配,并不会使用mmap。 只有你一次性申请超过KB是才会调用mmap
场景:申请5次内存,前3次申请小内存,后面2次申请超过KB,看看linux系统分配的内存是怎样的?
代码路径:\usr\cbasics_demo\1_malloc_Demo\4_malloc_demo.cpp
sbrk(0)会返回当前brk指针的位置。具体来说,它返回当前数据段的结束地址,也就是堆的顶端。当你调用sbrk(0)时,它实际上并不会改变brk指针的位置,只是返回当前brk指针的值。
可以看到上面的ptr1到ptr3内存地址很接近,说明是连续的,因为我写的代码申请的都是小内存,只有几个字母。
而从ptr4开始,内存地址完全变了,你可以理解pt3的分布还在秦皇岛,而pt4和pt5直接给你放北京了。
他们的区别就在于大小,pt4和pt5是超过KB的,由此可以证明这块的内存分配肯定是不同的。
而继续看Current brk的打印,这里打印的是当前进程内的内存地址:0xc 这很明显和pt1,pt2,pt3 都是在一块区域的,我觉得这足以证明 这三个是用的brk进行分配,而pt4和5没有用brk,因为brk的最新指针地址没有包含他俩。他俩的地址,早就超出了brk的android拉手网源码指针范围。
继续看释放哪里的打印,我分别释放了pt1一直到pt5,但是brk的指针地址,一点没变,还是0xc 说明,在底层free函数,不会立即释放内存,brk指针地址并没有改变。 下次申请内存时肯定会重复使用,所以它的性能比较高。
我基于这个demo画了个内存图,方便理解:
malloc函数,会调用brk和mmap(也就相当于syscall),所以性能测试只需触发malloc的小块内存和大块内存分配即可。测试场景如下:
(1)暴力基础测试,不考虑场景,直接测试申请内存效率
(2)触发malloc函数,持续申请小块内存,比如一个list集合或者数组数据,每个内容很小,但是加在一起很大,这时候我们是直接申请一大块内存,还是递增的申请小块内存呢?
(3)触发malloc函数的,大块内存申请,就是内存映射mmap,如果我创建的对象每个都很大,比如里面存储的是业务数据,一个对象就几百兆,那我是直接申请一大块内存做内存映射?还是将该对象拆分掉小块,去申请一堆小块内存呢?
使用malloc申请1万次小块内存,每个内存只有sizeof(char)大小。再使用mmap申请1万次内存,每次申请
*小块内存:0. 秒 大块内存:0. 秒 相差了了倍。
修改限制,不在使用次数,而是php源码授权模式固定大小,申请小块内存最大只申请MB,但是需要申请很多次,因为每次只是申请*sizeof(char)。
而大块内存每次申请:2** 但是最大申请MB。
结果:
小块:0. 秒 大块:0.秒 相差了倍
总结:从上面的实验得知,申请大块内存和申请小块内存在性能上并没有太大的区别,根本原因是申请次数,你申请大块内存是为了减少申请次数,并不是申请大块内存就快。同样的小块内存申请也一样,你申请的小,也不能频繁的申请,比如第二个场景,为了MB的空间,小块内存申请了万次,结果性能比申请大块内存相差了倍。
重点是:频率
对于内存分配的性能,通常需要考虑以下几个方面:
尝试分析小块内存申请情况
代码如下:
运行结果如下:
第一次打印的结果:
第二次打印的结果:
根据这些数据,我们可以初步分析内存碎片的情况:
malloc和free是C语言库函数,而在C++中常用的是new和delete,
C里面是用malloc_stats();
而C++则需要用/proc/self/smaps文件来查看进程的内存映射情况 ,但是大块内存无法用这个查看,比如mmap分配的。需要其他内存分析工具
A:他们直接的区别
new和delete是C++中的运算符,而malloc和free是C语言中的函数。它们之间有几个重要的区别:
总的来说,new和delete更适合在C++中使用,因为它们提供了更好的类型安全性、异常处理和对象构造/析构的支持。而在C语言中,或者需要与C代码进行交互时,可以使用malloc和free。
B:单纯性能的对比
从性能和原理的角度来看,new和delete与malloc和free之间也存在一些区别:
总的来说,从性能和原理的角度来看,new和delete在处理类对象和支持面向对象编程方面更加方便和安全,而malloc和free则更适合于处理简单的内存分配和释放操作。
然而在C++中,operator new通常会调用malloc来分配内存,但它并不是直接调用malloc函数。相反,C++标准库会提供operator new的重载版本,以便用户可以自定义内存分配行为。这意味着operator new可以使用不同的内存分配策略,而不仅仅是调用malloc。
因此,尽管new操作符在底层可能会使用operator new来执行内存分配,而operator new可能会使用malloc来分配内存,但new操作符并不会直接调用malloc函数。这种分层的设计使得C++的内存分配更加灵活,并且允许用户自定义内存分配策略。
最后这个总结我没法证明,毕竟还没看new的源码,现在查询到的资料看底层最终还是会到c的malloc函数上。
编译:g++ -o 5_2_pmTest_malloc_demo.o 5_2_pmTest_malloc_demo.cpp -lrt
运行: ./5_2_pmTest_malloc_demo.o
运行结果:可以看到C++并没有多太多。
C malloc and free time: 0. seconds
C++ new and delete time: 0. seconds
什么方法可以实现golang调用dll_golang调用dll方法
在Go语言中调用DLL方法有两种方式,一种是通过cgo包实现,另一种则是使用syscall包。接下来,我们将分别介绍这两种方法。
首先,我们来了解一下使用cgo包调用DLL方法的方法。cgo是Go语言提供的一个专门用于调用C代码的工具。在Go代码中,我们可以通过插入cgo注释来告诉Go编译器需要调用C代码。具体步骤如下:
第一步,创建一个包含需要调用DLL方法的C代码文件,保存为xxx.c。这个文件中需要包含DLL的头文件,以及需要调用的方法的声明。
第二步,使用命令go build编译Go源码,并生成可执行文件。
第三步,使用命令go run或者go build生成的可执行文件运行Go程序,调用DLL方法。
接下来,我们再来看一下使用syscall包调用DLL方法的方法。syscall包是Go语言提供的用于调用系统函数的包,我们可以利用它来调用DLL方法。具体步骤如下:
第一步,使用import导入syscall包。
第二步,使用syscall.LoadLibrary函数加载DLL。
第三步,使用syscall.GetProcAddress函数获取DLL方法的地址。
第四步,使用syscall.Syscall函数调用DLL方法。
如果你在使用DLL方法时遇到任何问题,建议使用一键智能修复DLL助手(点击即可下载)。它是一款专业的DLL修复工具,可以帮助你解决各种DLL相关的问题。
以上就是Go语言调用DLL方法的两种方法,希望能对你有所帮助。如果你还有其他问题或需要进一步的指导,请随时告诉我。
免杀动态对抗之syscall[源码分析]
基础概念
操作系统分为内核和应用层,从R0-R3,R0是内核,R3是用户层。Windows中日常调用的API都是R3抽象出来的接口,虽然Win API它也是R3接口,但由于Windows的设计思想就是高度封装,实际上的R3 API是ntdll.dll中的函数。
我们调用的Win API都是kernel.dll/user.dll中的函数,最终都要经过ntdll.dll。
逆向学习一个函数,选定CreateThread,ntdll中的对应函数是NtCreateThread。可以看到首先给eax赋值(系统调用号SSN),然后再执行syscall。
EDR的工作原理是对Windows API进行hook。一般使用inline hook,即将函数的开头地址值改成jmp xxxxxxx(hook函数地址)。知道了syscall的调用模板,自己构造syscall(获取SSN,syscall),即可绕过EDR对API的hook。
学习不同项目对应的手法,如HellsGate、TartarusGate、GetSSN、SysWhispers等。这些项目通过遍历解析ntdll.dll模块的导出表,定位函数地址,获取系统调用号SSN,实现动态获取SSN。
使用直接系统调用或间接系统调用,如SysWhispers系列项目的直接系统调用(手搓syscall asm)和间接系统调用(使用用户态API,如kernel.dll中的API)。系统调用号SSN在不同版本的系统下是不一样的,可以参考相关技术博客整理的列表。
SysWhispers2使用随机系统调用跳转(Random Syscall Jumps)避免“系统调用的标记”,通过SW__GetRandomSyscallAddress函数在ntdll.dll中搜索并选择一个干净的系统调用指令来使用。
SysWhispers3引入了egg技术(动态字符替换,汇编指令层次的混淆)和支持直接跳转到syscalls,是spoof call的变体,绕过对用户态asm文件syscall的监控。
HWSyscalls项目通过kernel gadget,跳到ntdll.dll中做间接syscall,更彻底地实现了间接系统调用。
这些项目的实现涉及软件中自定义堆栈流程、硬件断点等技术,通过内核函数调用、动态字符替换、异常处理机制等,绕过EDR检测和系统调用监控,实现免杀动态对抗。
Go语言源码阅读分析(3)-- unsafe
Go语言的unsafe包提供了一套打破类型安全限制的操作,但使用时需谨慎,因为它可能导致代码无法移植。包内主要包含unsafe.go文件和一些声明,实际实现和测试用例并未提供。关键内容如下: 1. Pointer类型:可以转换为任何类型的指针,以及Uintptr类型,这种转换允许直接读写内存,风险极高,需谨慎使用。 - 可以将任意类型转换为Pointer类型,但转换后不能长于原类型,且要求内存布局一致。例如,将float转换为uint的函数`Floatbits`。 - Pointer可以转换为uintptr,但这种转换仅用于内存地址的打印,且不能直接从uintptr恢复为Pointer,除非是枚举类型。 2. 偏移指针:用于访问结构体字段或数组元素,需确保指针不会超出原始对象的内存范围。 3. syscall调用:在syscall包中,某些函数需要在同一条语句中进行指针到uintptr的转换,以确保指针指向的对象在调用时仍然有效。 4. reflect包使用:reflect.Value.Pointer和UndafeAddr返回的都是uintptr,应在获取后立即转换为Pointer,避免对象被GC回收。 5. 反射结构体转换:例如StringHeader和SliceHeader的Data字段,仅在指向活动切片或字符串时有效。 总之,unsafe包的使用需遵循特定的规则和限制,不当使用可能导致程序不稳定或移植问题。接下来的计划是研究reflect包。剖析Linux内核源码解读之《实现fork研究(一)》
Linux内核源码解析:深入探讨fork函数的实现机制(一)
首先,我们关注的焦点是fork函数,它是Linux系统创建新进程的核心手段。本文将深入剖析从用户空间应用程序调用glibc库,直至内核层面的具体过程。这里假设硬件平台为ARM,使用Linux内核3..3和glibc库2.版本。这些版本的库和内核代码可以从ftp.gnu.org获取。
在glibc层面,针对不同CPU架构,进入内核的步骤有所不同。当glibc准备调用kernel时,它会将参数放入寄存器,通过软中断(SWI) 0x0指令进入保护模式,最终转至系统调用表。在arm平台上,系统调用表的结构如下:
系统调用表中的CALL(sys_clone)宏被展开后,会将sys_clone函数的地址放入pc寄存器,这个函数实际由SYSCALL_DEFINEx定义。在do_fork函数中,关键步骤包括了对父进程和子进程的跟踪,以及对子进程进行初始化,包括内存分配和vfork处理等。
总的来说,调用流程是这样的:应用程序通过软中断触发内核处理,通过系统调用表选择并执行sys_clone,然后调用do_fork函数进行具体的进程创建操作。do_fork后续会涉及到copy_process函数,这个函数是理解fork核心逻辑的重要入口,包含了丰富的内核知识。在后续的内容中,我将深入剖析copy_process函数的工作原理。
2024-11-18 19:54
2024-11-18 19:39
2024-11-18 19:22
2024-11-18 19:01
2024-11-18 18:52