通过“LD_PRELOAD”替换“malloc”、“calloc”、“realloc”和“free”时出现分段错误 背景关于功能的简短技术说明问题注意输出glibc 版本代码 mem.c使用 /usr/bin/env 后的 Valgrind 输出

问题描述

背景

我正在尝试通过 malloc(3) 环境变量替换 calloc(3)/realloc(3)/free(3)/LD_PRELOAD。我尝试使用静态链接自定义函数,它们工作得很好。

但是,当我将它作为共享库附加到 LD_PRELOAD 时,它总是导致段错误


关于功能的简短技术说明

  • 我对 mmap(2)munmap(2) 使用 Linux x86-64 malloc(3)free(3) 系统调用
  • calloc(3) 只是调用 malloc(3) 并进行乘法溢出检查。
  • realloc(3) 调用 malloc(3),然后将旧数据复制到新分配的内存并取消映射旧内存。

问题

  • 我的方法有什么问题,总是导致段错误
  • 如何调试它(gdb 和 valgrind 也有段错误)?
  • 在这里错过了什么?

注意

我非常清楚,每次 mmap 调用都使用 malloc一个坏主意,尤其是对于性能而言。我只想知道为什么我的方法不起作用。


输出

ammarfaizi2@integral:~$ gcc -shared mem.c -O3 -o my_mem.so
ammarfaizi2@integral:~$ LD_PRELOAD=$(pwd)/my_mem.so ls
Segmentation fault (core dumped)
ammarfaizi2@integral:~$ LD_PRELOAD=$(pwd)/my_mem.so cat
Segmentation fault (core dumped)
ammarfaizi2@integral:~$ LD_PRELOAD=$(pwd)/my_mem.so w
Segmentation fault (core dumped)
ammarfaizi2@integral:~$ LD_PRELOAD=$(pwd)/my_mem.so gdb ls
Segmentation fault (core dumped)
ammarfaizi2@integral:~$ LD_PRELOAD=$(pwd)/my_mem.so valgrind ls
Segmentation fault (core dumped)
ammarfaizi2@integral:~$ 

glibc 版本

ammarfaizi2@integral:~$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.33-0ubuntu2) release release version 2.33.
copyright (C) 2021 Free Software Foundation,Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or fitness FOR A
PARTIculaR PURPOSE.
Compiled by GNU CC version 10.2.1 20210130.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions,please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
ammarfaizi2@integral:~$ 

代码 mem.c


#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>
#include <string.h>


static inline void *my_mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset)
{
    void *ret;
    register int _flags asm("r10") = flags;
    register int _fd asm("r8") = fd;
    register off_t _offset asm("r9") = offset;

    asm volatile(
        "syscall"
        : "=a"(ret)
        : "a"(9),"D"(addr),"S"(length),"d"(prot),"r"(_flags),"r"(_fd),"r"(_offset)
        : "memory","r11","rcx"
    );
    return ret;
}


static inline int my_munmap(void *addr,size_t length)
{
    int ret;

    asm volatile(
        "syscall"
        : "=a"(ret)
        : "a"(11),"S"(length)
        : "memory","rcx"
    );
    return ret;
}

#define unlikely(EXPR) __builtin_expect(EXPR,0)

void * __attribute__((noinline)) malloc(size_t len)
{
    void *start_map;
    uintptr_t user_ptr,cmperr;
    size_t add_req = 0;

    add_req += sizeof(size_t);
    add_req += sizeof(uint8_t);
    add_req += 0x1full;

    start_map = my_mmap(NULL,add_req + len,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1,0);

    cmperr = 0xffffffffffffff00ull;
    if (unlikely(((uintptr_t)start_map & cmperr) == cmperr)) {
        errno = ENOMEM;
        return NULL;
    }

    /* Align 32-byte and take space to save the length and diff */
    user_ptr = ((uintptr_t)start_map + add_req) & ~0x1full;
    *(size_t  *)(user_ptr - 8) = len;
    *(uint8_t *)(user_ptr - 9) = (uint8_t)(user_ptr - (uintptr_t)start_map);

    return (void *)user_ptr;
}


void free(void *__user_ptr)
{
    size_t len;
    uint8_t diff;
    uintptr_t user_ptr = (uintptr_t)__user_ptr;

    len  = *(size_t  *)(user_ptr - 8);
    diff = *(uint8_t *)(user_ptr - 9);
    my_munmap((void *)(user_ptr - diff),len);
}


void *realloc(void *__user_ptr,size_t new_len)
{
    void *new_mem;
    size_t len;
    uint8_t diff;
    uintptr_t user_ptr = (uintptr_t)__user_ptr;

    len  = *(size_t  *)(user_ptr - 8);
    diff = *(uint8_t *)(user_ptr - 9);

    new_mem = malloc(new_len);
    if (unlikely(new_mem == NULL))
        return NULL;

    memcpy(new_mem,__user_ptr,(new_len < len) ? new_len : len);
    my_munmap((void *)(user_ptr - diff),len);
    return new_mem;
}


void *calloc(size_t nmemb,size_t len)
{
    size_t x = nmemb * len;
    if (unlikely(nmemb != 0 && x / nmemb != len)) {
        errno = EOVERFLOW;
        return NULL;
    }
    return malloc(x);
}

// #include <stdio.h>
// int main(void)
// {
//  char *test = malloc(1);

//  for (size_t i = 2; i <= (1024 * 1024); i++) {
//      test = realloc(test,i);
//      memset(test,'q',i);
//  }

//  free(test);
// }


使用 -Wall -Wextra -ggdb3strace 输出重新编译

ammarfaizi2@integral:~$ 
ammarfaizi2@integral:~$ gcc -Wall -Wextra -ggdb3 -shared mem.c -O3 -o my_mem.so
ammarfaizi2@integral:~$ strace -tf /usr/bin/env LD_PRELOAD=$(pwd)/my_mem.so ls
12:59:15 execve("/usr/bin/env",["/usr/bin/env","LD_PRELOAD=/home/ammarfaizi2/my_"...,"ls"],0x7ffd2d6ee188 /* 34 vars */) = 0
12:59:15 brk(NULL)                      = 0x565552193000
12:59:15 arch_prctl(0x3001 /* ARCH_??? */,0x7ffd13017120) = -1 EINVAL (Invalid argument)
12:59:15 access("/etc/ld.so.nohwcap",F_OK) = -1 ENOENT (No such file or directory)
12:59:15 access("/etc/ld.so.preload",R_OK) = -1 ENOENT (No such file or directory)
12:59:15 openat(AT_FDCWD,"/etc/ld.so.cache",O_RDONLY|O_CLOEXEC) = 3
12:59:15 newfstatat(3,"",{st_mode=S_IFREG|0644,st_size=74118,...},AT_EMPTY_PATH) = 0
12:59:15 mmap(NULL,74118,PROT_READ,MAP_PRIVATE,3,0) = 0x7facba2ba000
12:59:15 close(3)                       = 0
12:59:15 access("/etc/ld.so.nohwcap",F_OK) = -1 ENOENT (No such file or directory)
12:59:15 openat(AT_FDCWD,"/lib/x86_64-linux-gnu/libc.so.6",O_RDONLY|O_CLOEXEC) = 3
12:59:15 read(3,"\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\206\2\0\0\0\0\0"...,832) = 832
12:59:15 pread64(3,"\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"...,784,64) = 784
12:59:15 pread64(3,"\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"...,48,848) = 48
12:59:15 pread64(3,"\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30\355\366\266\203\242\371v\214\300\356\234\306J\346\373"...,68,896) = 68
12:59:15 newfstatat(3,{st_mode=S_IFREG|0755,st_size=1983576,8192,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,0) = 0x7facba2b8000
12:59:15 pread64(3,64) = 784
12:59:15 mmap(NULL,2012056,MAP_PRIVATE|MAP_DENYWRITE,0) = 0x7facba0cc000
12:59:15 mmap(0x7facba0f2000,1486848,PROT_READ|PROT_EXEC,MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE,0x26000) = 0x7facba0f2000
12:59:15 mmap(0x7facba25d000,311296,0x191000) = 0x7facba25d000
12:59:15 mmap(0x7facba2a9000,24576,0x1dc000) = 0x7facba2a9000
12:59:15 mmap(0x7facba2af000,33688,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,0) = 0x7facba2af000
12:59:15 close(3)                       = 0
12:59:15 mmap(NULL,0) = 0x7facba0ca000
12:59:15 arch_prctl(ARCH_SET_FS,0x7facba2b95c0) = 0
12:59:15 mprotect(0x7facba2a9000,12288,PROT_READ) = 0
12:59:15 mprotect(0x565550cb5000,4096,PROT_READ) = 0
12:59:15 mprotect(0x7facba2ff000,PROT_READ) = 0
12:59:15 munmap(0x7facba2ba000,74118)  = 0
12:59:15 brk(NULL)                      = 0x565552193000
12:59:15 brk(0x5655521b4000)            = 0x5655521b4000
12:59:15 openat(AT_FDCWD,"/usr/lib/locale/locale-archive",st_size=3041456,3041456,0) = 0x7facb9de3000
12:59:15 close(3)                       = 0
12:59:15 execve("/home/ammarfaizi2/.local/bin/ls",["ls"],0x565552194550 /* 35 vars */) = -1 ENOENT (No such file or directory)
12:59:15 execve("/usr/local/sbin/ls",0x565552194550 /* 35 vars */) = -1 ENOENT (No such file or directory)
12:59:15 execve("/usr/local/bin/ls",0x565552194550 /* 35 vars */) = -1 ENOENT (No such file or directory)
12:59:15 execve("/usr/sbin/ls",0x565552194550 /* 35 vars */) = -1 ENOENT (No such file or directory)
12:59:15 execve("/usr/bin/ls",0x565552194550 /* 35 vars */) = 0
12:59:15 brk(NULL)                      = 0x557f8624b000
12:59:15 arch_prctl(0x3001 /* ARCH_??? */,0x7fff39dc1a30) = -1 EINVAL (Invalid argument)
12:59:15 access("/etc/ld.so.nohwcap","/home/ammarfaizi2/my_mem.so","\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\20\0\0\0\0\0\0"...,832) = 832
12:59:15 newfstatat(3,{st_mode=S_IFREG|0700,st_size=58768,0) = 0x7fa7daa68000
12:59:15 mmap(NULL,16448,0) = 0x7fa7daa63000
12:59:15 mmap(0x7fa7daa64000,0x1000) = 0x7fa7daa64000
12:59:15 mmap(0x7fa7daa65000,0x2000) = 0x7fa7daa65000
12:59:15 mmap(0x7fa7daa66000,0x2000) = 0x7fa7daa66000
12:59:15 close(3)                       = 0
12:59:15 access("/etc/ld.so.preload",0) = 0x7fa7daa50000
12:59:15 close(3)                       = 0
12:59:15 access("/etc/ld.so.nohwcap","/lib/x86_64-linux-gnu/libselinux.so.1","\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 p\0\0\0\0\0\0"...,st_size=167352,178664,0) = 0x7fa7daa24000
12:59:15 mmap(0x7fa7daa2a000,106496,0x6000) = 0x7fa7daa2a000
12:59:15 mmap(0x7fa7daa44000,32768,0x20000) = 0x7fa7daa44000
12:59:15 mmap(0x7fa7daa4c000,0x27000) = 0x7fa7daa4c000
12:59:15 mmap(0x7fa7daa4e000,6632,0) = 0x7fa7daa4e000
12:59:15 close(3)                       = 0
12:59:15 access("/etc/ld.so.nohwcap",AT_EMPTY_PATH) = 0
12:59:15 pread64(3,0) = 0x7fa7da838000
12:59:15 mmap(0x7fa7da85e000,0x26000) = 0x7fa7da85e000
12:59:15 mmap(0x7fa7da9c9000,0x191000) = 0x7fa7da9c9000
12:59:15 mmap(0x7fa7daa15000,0x1dc000) = 0x7fa7daa15000
12:59:15 mmap(0x7fa7daa1b000,0) = 0x7fa7daa1b000
12:59:15 close(3)                       = 0
12:59:15 access("/etc/ld.so.nohwcap","/lib/x86_64-linux-gnu/libpcre2-8.so.0","\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340\"\0\0\0\0\0\0"...,st_size=617160,619304,0) = 0x7fa7da7a0000
12:59:15 mmap(0x7fa7da7a2000,438272,0x2000) = 0x7fa7da7a2000
12:59:15 mmap(0x7fa7da80d000,167936,0x6d000) = 0x7fa7da80d000
12:59:15 mmap(0x7fa7da836000,0x95000) = 0x7fa7da836000
12:59:15 close(3)                       = 0
12:59:15 access("/etc/ld.so.nohwcap","/lib/x86_64-linux-gnu/libdl.so.2","\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \"\0\0\0\0\0\0"...,st_size=22912,24848,0) = 0x7fa7da799000
12:59:15 mmap(0x7fa7da79b000,0x2000) = 0x7fa7da79b000
12:59:15 mmap(0x7fa7da79d000,0x4000) = 0x7fa7da79d000
12:59:15 mmap(0x7fa7da79e000,0x4000) = 0x7fa7da79e000
12:59:15 close(3)                       = 0
12:59:15 access("/etc/ld.so.nohwcap","/lib/x86_64-linux-gnu/libpthread.so.0","\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300\200\0\0\0\0\0\0"...,792) = 48
12:59:15 pread64(3,"\4\0\0\0\24\0\0\0\3\0\0\0GNU\0a7\363\352;k|\2228\244\6\253\346\2569\312"...,840) = 68
12:59:15 newfstatat(3,st_size=150456,136208,0) = 0x7fa7da777000
12:59:15 mmap(0x7fa7da77e000,65536,0x7000) = 0x7fa7da77e000
12:59:15 mmap(0x7fa7da78e000,20480,0x17000) = 0x7fa7da78e000
12:59:15 mmap(0x7fa7da793000,0x1b000) = 0x7fa7da793000
12:59:15 mmap(0x7fa7da795000,13328,0) = 0x7fa7da795000
12:59:15 close(3)                       = 0
12:59:15 mmap(NULL,0) = 0x7fa7da775000
12:59:15 mmap(NULL,0) = 0x7fa7da772000
12:59:15 arch_prctl(ARCH_SET_FS,0x7fa7da772800) = 0
12:59:15 mprotect(0x7fa7daa15000,PROT_READ) = 0
12:59:15 mprotect(0x7fa7da793000,PROT_READ) = 0
12:59:15 mprotect(0x7fa7da79e000,PROT_READ) = 0
12:59:15 mprotect(0x7fa7da836000,PROT_READ) = 0
12:59:15 mprotect(0x7fa7daa4c000,PROT_READ) = 0
12:59:15 mprotect(0x7fa7daa66000,PROT_READ) = 0
12:59:15 mprotect(0x557f842f9000,PROT_READ) = 0
12:59:15 mprotect(0x7fa7daa9c000,PROT_READ) = 0
12:59:15 munmap(0x7fa7daa50000,74118)  = 0
12:59:15 set_tid_address(0x7fa7da772ad0) = 1639769
12:59:15 set_robust_list(0x7fa7da772ae0,24) = 0
12:59:15 rt_sigaction(SIGRTMIN,{sa_handler=0x7fa7da77eb70,sa_mask=[],sa_flags=SA_RESTORER|SA_SIGINFO,sa_restorer=0x7fa7da78b160},NULL,8) = 0
12:59:15 rt_sigaction(SIGRT_1,{sa_handler=0x7fa7da77ec10,sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO,8) = 0
12:59:15 rt_sigprocmask(SIG_UNBLOCK,[RTMIN RT_1],8) = 0
12:59:15 prlimit64(0,RLIMIT_STACK,{rlim_cur=8192*1024,rlim_max=RLIM64_INFINITY}) = 0
12:59:15 statfs("/sys/fs/selinux",0x7fff39dc1a00) = -1 ENOENT (No such file or directory)
12:59:15 statfs("/selinux",0x7fff39dc1a00) = -1 ENOENT (No such file or directory)
12:59:15 mmap(NULL,512,0) = 0x7fa7daa9b000
12:59:15 openat(AT_FDCWD,"/proc/filesystems",O_RDONLY|O_CLOEXEC) = 3
12:59:15 mmap(NULL,160,0) = 0x7fa7daa62000
12:59:15 newfstatat(3,{st_mode=S_IFREG|0444,st_size=0,1064,0) = 0x7fa7daa61000
12:59:15 read(3,"nodev\tsysfs\nnodev\ttmpfs\nnodev\tbd"...,1024) = 410
12:59:15 read(3,1024)              = 0
12:59:15 munmap(0x7fa7daa62000,120)    = 0
12:59:15 close(3)                       = 0
12:59:15 munmap(0x7fa7daa61000,1024)   = 0
12:59:15 munmap(0x7fa7daa9b000,472)    = 0
12:59:15 --- SIGSEGV {si_signo=SIGSEGV,si_code=SEGV_MAPERR,si_addr=0xfffffffffffffff7} ---
12:59:16 +++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
ammarfaizi2@integral:~$ 

使用 /usr/bin/env 后的 Valgrind 输出

ammarfaizi2@integral:~$ valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all /usr/bin/env LD_PRELOAD=$(pwd)/my_mem.so ls
==1640100== Memcheck,a memory error detector
==1640100== copyright (C) 2002-2017,and GNU GPL'd,by Julian Seward et al.
==1640100== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==1640100== Command: /usr/bin/env LD_PRELOAD=/home/ammarfaizi2/my_mem.so ls
==1640100== 
Segmentation fault (core dumped)
ammarfaizi2@integral:~$ 

解决方法

我已经调试了核心文件并修复了崩溃,对于 free 函数,您需要检查参数是否为 nullptr,对于 realloc 我们需要处理 __user_ptr nullptr 也是。

void free(void *__user_ptr) {
  if (!__user_ptr) return;
  // ...
}

void *realloc(void *__user_ptr,size_t new_len) {
  void *new_mem;
  size_t len;
  uint8_t diff;
  uintptr_t user_ptr = (uintptr_t)__user_ptr;

  new_mem = malloc(new_len);
  if (!__user_ptr) return new_mem;
  // ....
}

我在编写内存分配器库方面有一些经验。在调试过程中,我发现一些旧的 c 程序使用 realloc 作为 mallocnullptr 参数,这很奇怪但完全有效,请参考手册页

realloc() 函数改变指向的内存块的大小 通过 ptr 调整字节大小。内容将在以下范围内保持不变 区域的开始直到旧大小和新大小中的最小值。如果 新的大小大于旧的大小,增加的内存不会 初始化。如果 ptr 为 NULL,则调用等效于 malloc(size),对于大小的所有值;如果大小等于零,并且 ptr 不为NULL,则调用等价于free(ptr)。除非 ptr 为 NULL,它一定是由较早的 malloc() 调用返回的, calloc() 或 realloc()。如果指向的区域被移动,一个 free(ptr) 完成了。

顺便说一下,我看到您尝试将 syscallmmap 包装起来,我建议我们将它们替换为 https://github.com/linux-on-ibm-z/linux-syscall-support ,这是一个产生式级包装库并被广泛使用。我认为我们应该尽可能少写代码以减少潜在的错误。

,

gcc -Wall -Wextra -ggdb3 -shared mem.c -O3 -o my_mem.so

是错误的,如果你想建立一个共享库。参见 dlopen(3)elf(5)ld.so(8)

您实际上需要一个 position-independent-code 文件,因此请使用

gcc -Wall -Wextra -ggdb3 -fPIC -shared mem.c -O3 -o my_mem.so

阅读Advanced Linux Programming、Drepper 的论文How to write shared libraries 以及Program Library HowToLinux Assembler Howto

顺便说一句,你的 my_mmap 很幼稚,不处理 mmap(2) 的失败情况。请参阅 errno(3)syscalls(2)

您可能想研究(以获得灵感)GNU libcmusl-libc 的源代码。两者都有比您更好的 mmap 实现。

您应该考虑使用 Clang static analyzer,编写自己的 GCC plugin(或使用 Bismon),并使用 GCC address sanitizer。花更多时间阅读Invoking GCC

附注。 2021 年,另见 DECODER 项目。