0%

gitignore不生效

参考:链接

file mode 10064的含义

参考博客

环境变量

  • environ - 用户环境,一个全局变量。头文件 <unistd.h>。可以通过 man environ 查看手册。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <unistd.h>
    #include <iostream>
    using namespace std;

    int main() {
    cout << "program environment: " << endl;
    for (char** entry = environ; *entry; ++entry) {
    cout << *entry << endl;
    }
    }
  • getenv - 获取环境变量。头文件 <stdlib.h>

  • setenv - 设置环境变量。头文件 stdlib.h

gdb实现原理

参考链接

gdb命令

  • thread apply [threadno] [all] args - 将命令传递给一个或多个线程,参见链接
    比如,thread apply all continue表示将continue命令传递给所有线程,也就是让所有线程都继续运行。

  • rbreak - Set a breakpoint for all functions matching REGEXP. 参考链接

    e.g. rbreak file.C:.* - 给file.C的所有函数加上断点。

  • info

    • info inferior - 可以查看当前调试的进程的PID。另外一种方法是在gdb命令行中直接调用C函数:print (int)getpid()。参考:链接
    • info source - 当前调试的源文件路径。
    • info proc - 当前进程信息
      • info proc files - 当前进程打开的文件(和文件描述符)。
  • attach - 连接到正在运行的进程。与gdb -p效果相同。

  • detach - 取消连接的进程。

  • handle <signal> print pass nostop - 捕获信号(比如SIGSEGV)并且忽略它。handle <signal nostop

  • set - 修改变量的值,比如set x=10(或set var x=10)将变量x的值改为10。参考博客

  • show directories

  • print - gdb默认设置打印字符串的长度为200;更改打印最大长度:set print elements <number-of-elements>0表示unlimited.

  • ptype <variable name> - 打印变量类型。

  • finish - 从函数中返回,并打印函数返回值(即使函数的return语句很复杂,也可以获取返回值)。

环境变量

链接

断点

添加断点:

1
break file:line_no

查看断点:

1
info break

删除第2个断点:

1
delete 2

条件断点

参考:博客文档

break ... if cond

观察断点

捕捉断点

1
try...catch

打印长度的限制

  • Value sizes - 参考:文档
1
2
set max-value-size bytes
set max-value-size unlimited
  • 打印字符长度限制

    gdb默认设置打印字符串的长度为200;更改打印最大长度:set print elements

coredump

gdb命令:gcore

Reference

WSL无法使用gdb

WSL指Windows虚拟机。

解决方法

安装PPA的daily build版本

1
2
3
sudo add-apt-repository ppa:ubuntu-support-team/gdb
sudo apt update
sudo apt install gdb

gdb attach 权限报错

This is due to kernel hardening in Linux; you can disable this behavior by echo 0 > /proc/sys/kernel/yama/ptrace_scope or by modifying it in /etc/sysctl.d/10-ptrace.conf.

How to solve “ptrace operation not permitted” when trying to attach GDB to a process?

gdb debug forks

Reference

By default, when a program forks, gdb will continue to debug the parent process and the child process will run unimpeded.

If you want to follow the child process instead of the parent process, use the command set follow-fork-mode.

set follow-fork-mode mode
Set the debugger response to a program call of fork or vfork. A call to fork or vfork creates a new process. The mode argument can be:
parent
The original process is debugged after a fork. The child process runs unimpeded. This is the default.
child
The new process is debugged after a fork. The parent process runs unimpeded.
ask
gdb 会提示让你选择 parent 还是 child

show follow-fork-mode
Display the current debugger response to a fork or vfork call.
On Linux, if you want to debug both the parent and child processes, use the command set detach-on-fork.

set detach-on-fork mode
Tells gdb whether to detach one of the processes after a fork, or retain debugger control over them both.
on
The child process (or parent process, depending on the value of follow-fork-mode) will be detached and allowed to run independently. This is the default.
off
Both processes will be held under the control of gdb. One process (child or parent, depending on the value of follow-fork-mode) is debugged as usual, while the other is held suspended.

show detach-on-fork
Show whether detach-on-fork mode is on/off.

If you issue a run command to gdb after an exec call executes, the new target restarts. To restart the parent process, use the file command with the parent executable name as its argument. By default, after an exec call executes, gdb discards the symbols of the previous executable image. You can change this behaviour with the set follow-exec-mode command.

set follow-exec-mode mode
Set debugger response to a program call of exec. An exec call replaces the program image of a process.
follow-exec-mode can be:

new
gdb creates a new inferior and rebinds the process to this new inferior. The program the process was running before the exec call can be restarted afterwards by restarting the original inferior.
For example:

1
2
3
4
5
6
7
8
9
10
11
(gdb) info inferiors
(gdb) info inferior
Id Description Executable
* 1 <null> prog1
(gdb) run
process 12020 is executing new program: prog2
Program exited normally.
(gdb) info inferiors
Id Description Executable
* 2 <null> prog2
1 <null> prog1

same
gdb keeps the process bound to the same inferior. The new executable image replaces the previous executable loaded in the inferior. Restarting the inferior after the exec call, with e.g., the run command, restarts the executable the process was running after the exec call. This is the default mode.
For example:

1
2
3
4
5
6
7
8
9
(gdb) info inferiors
Id Description Executable
* 1 <null> prog1
(gdb) run
process 12020 is executing new program: prog2
Program exited normally.
(gdb) info inferiors
Id Description Executable
* 1 <null> prog2

Setting Catchpoints

Reference

gdb redirect to a log file

You need to enable logging:

1
2
3
4
5
6
7
(gdb) set logging on
Now GDB will log to ./gdb.txt. You can tell it which file to use:

(gdb) set logging file my_god_object.log
And you can examine the current logging configuration:

(gdb) show logging

记录输入的命令:

1
(gdb) set trace-commands on

Refercence

递归处理顺序

gcc 的输入文件和库是从左往右处理的。也就是说,以下命令是错误的:

1
gcc -L. -la main.cc

链接器处理到某个目标文件(如 main.cc 编译后的目标代码)时,如果遇到未解析的符号(比如 f() ),
它会从接下来的库中查找这些符号。因此顺序非常重要。

这里,-L. -la 选项在 main.cc 之前,链接器会首先尝试从 liba.so 中查找引用的符号,
但是,因为此时 main.cc 还未被处理,所以链接器还不知道有对 liba.so 中的函数 f() 的引用。
到了 main.cc ,链接器解析出引用,但它不会回头再去 liba.so 中查找,导致报错:”undefined reference to f()”。

正确的命令如下:

1
gcc main.cc -L. -la

注意:liba.so 的指定必须去掉 lib 和 .so ,也就是说不允许直接指定“库文件名”,而是只能指定“库名”。
如果想直接指定库文件名,那么应该把 liba.so 当成输入文件:

1
gcc main.cc ./liba.so

属性语法(Attribute Syntax)

参考:官方文档

函数属性(Function Attributes)

参考:

属性列举:

  • format (archetype, string-index, first-to-check)

    format 属性,指定函数采用 printf、scanf、strftime 或 strfmon 风格的参数,
    这些参数应根据格式字符串(format string)进行类型检查(type-checked)。
    类型检查发生在编译期。

    举例:

    1
    2
    3
    extern int
    my_printf (void *my_object, const char *my_format, ...)
    __attribute__ ((format (printf, 2, 3)));
    • archetype决定format string应该如何解释。
      可选为printfscanfstrftimestrfmon(也可以使用__printf____scanf____strftime____strfmon__)。
    • string-index指定哪个参数是format string(从1开始)。
    • first-to-check指定format string对应的第一个参数的序号。
      对于那些无法检查参数的函数(比如vprintf),该参数指定为0。在这种情况下,编译器仅检查format string的一致性。对于strftime格式,该参数必须为0

选项

  • -save-temps: 可以保留所有中间文件,例如预编译文件、汇编文件、目标文件等。

ulimit

1
2
3
4
#查看配置
ulimit -a
#设置core file size
ulimit -c unlimited

ulimit只对当前终端有效。

以下两种方法对所有用户和终端有效:

  1. /etc/security/limits.conf中设置(redhat衍生系linux)。
  2. 或注释掉/etc/profile中的这一行:
    1
    2
    # No core files by default
    ulimit -S -c 0 > /dev/null 2>&1

core_pattern

core_pattern解释

链接

Read /usr/src/linux/Documentation/sysctl/kernel.txt.

core_pattern is used to specify a core dumpfile pattern name.

在系统启动时,Apport(crash reporting service)会生成配置文件/proc/sys/kernel/core_pattern。参考这里

Apport uses /proc/sys/kernel/core_pattern to directly pipe the core dump into apport:

1
2
3
$ cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c
$

Note that even if ulimit is set to disabled core files (by specyfing a core file size of zero using ulimit -c 0), apport will still capture the crash.

For intercepting Python crashes it installs a /etc/python*/sitecustomize.py to call apport on unhandled exceptions.

其中,/usr/share/apport/apport是一个python脚本。

以下是core_pattern文件的参数说明(参考Linux Manual Page:man core):

1
2
3
4
5
6
7
8
9
10
%c - Core file size soft resource limit of crashing process (since Linux 2.6.24).
%p - insert pid into filename 添加pid
%u - insert current uid into filename 添加当前uid
%g - insert current gid into filename 添加当前gid
%s - insert signal that caused the coredump into the filename 添加导致产生core的信号
%t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间
%h - insert hostname where the coredump happened into filename 添加主机名
%e - insert coredumping executable name into filename 添加命令名

If the first character of the pattern is a '|', the kernel will treat the rest of the pattern as a command to run. The core dump will be written to the standard input of that program instead of to a file.

Apport的拦截组件默认是关闭的:

Apport itself is running at all times because it collects crash data for whoopsie (see ErrorTracker). However, the crash interception component is still disabled. To enable it permanently, do:

1
sudo nano /etc/apport/crashdb.conf

… and add a hash symbol # in the beginning of the following line:

'problem_types': ['Bug', 'Package'],

To disable crash reporting just remove the hash symbol.

设置core_pattern

链接

  1. /proc/sys/kernel/core_uses_pid可以控制产生的core文件的文件名中是否添加pid作为扩展,如果添加则文件内容为1,否则为0;

  2. /proc/sys/kernel/core_pattern可以设置格式化的core文件保存位置或文件名:

    1
    2
    3
    $ cat /proc/sys/kernel/core_pattern
    |/usr/share/apport/apport %p %s %c
    $ echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern

    你可以用下列方式来完成:

    1
    2
    3
    4
    #查看所有sysctl所有变量的值。
    sysctl -a
    #设置变量kernel.core_pattern为如下值。
    sudo sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t

    这些操作一旦计算机重启,则会丢失,如果你想持久化这些操作,可以在 /etc/sysctl.conf文件中增加:

    1
    kernel.core_pattern=/tmp/core%p

    加好后,如果你想不重启看看效果的话,则用下面的命令:

    1
    sysctl -p /etc/sysctl.conf

相关命令行工具

参考资料:

Linux Manual Page: man core

SEE ALSO
    bash(1),  coredumpctl(1),  gdb(1),  getrlimit(2), mmap(2), prctl(2), sigaction(2), elf(5), proc(5), pthreads(7), signal(7), systemd-coredump(8)

Segment Fault排查

参考链接

  1. dmesg + nm + addr2line

    addr2line只能找出executable的行号;如果是shared libraries,请使用gdb。参考这里

    dmesg输出的含义:

    ip: 表示instruction pointer.

  2. fprintf

  3. gdb

  4. signal(SIGSEGV,handler)

  5. valgrind
    参考:
    博客
    文档
    可以使用PostScript查看图形化结果

  6. heaptrack

    github仓库

  7. Jemalloc

    官网

编译主要步骤

参考博客

  1. 预处理
1
gcc -E test.c -o test.i
  1. 编译

    编译是将高级语言(例如C、C++、Jave等)代码转换成机器码的过程。
    编译可以分成多个阶段,包括词法分析、语法分析、语义分析、优化和代码生成。
    编译器首先将源代码转换一种中间表示(通常是汇编代码或字节码),然后再将其转换为目标机器的机器代码。
    经过编译的代码通常是二进制的,可以直接在目标机器上执行。

先生成汇编代码:

1
gcc -S test.i -o test.s
  1. 汇编

    将汇编代码转成机器码。

1
gcc -c test.s -o test.o
  1. 链接

    该目标文件与其他目标文件、库文件、启动文件等链接起来生成可执行文件。

1
gcc test.o -o test

Note: 可以使用 -save-temps 选项以保留编译过程中的所有中间文件:

1
gcc -save-temps test.c

链接

C++名称修饰

Name mangling (C++ only): 名称修饰,也称为名称重整、名称改编。参见链接12

环境变量

参考:man ld.so

PATH
LD_LIBRARY_PATH
LD_PRELOAD  在程序运行时,强制优先加载指定的共享库,即使系统中已有其他版本。常用于调试、性能分析、函数拦截(hook)等。
            只在运行时生效。
            优先级高于 rpath、LD_LIBRARY_PATH 等。
            可以用来“注入”库,修改程序行为。

函数

命令

参考:man ld.so, man vdso, man elf, scanelf

ld(1), ldd(1), pldd(1), sprof(1), dlopen(3),getauxval(3), elf(5), capabilities(7),
rtld-audit(7), ldconfig(8), sln(8), vdso(7), as(1), elfedit(1), gdb(1), nm(1),
objcopy(1), objdump(1), patchelf(1), readelf(1), size(1), strings(1), strip(1),
execve(2), dl_iterate_phdr(3), core(5), ld.so(8)

ldconfig

配置动态连接器(dynamic linker)的运行时绑定(dynamic bindings)。
如果你刚刚安装好共享库,可能需要运行ldconfig:
    sudo ldconfig
通常需要超级用户来运行ldconfig,因为可能对需要某些root用户所有的目录和文件有写入权限。
lddconfig 为从以下目录找到的共享库创建必要的 links 和 cache :
    command line指定的目录;
    /etc/ld.so.conf文件中指定的目录;
    受信任的目录: /lib, /lib64, /usr/lib, /usr/lib64 。
该 cache 被运行时连接器(run-time linker) ld.so 或 ld-linux.so 使用。

ldconfig 尝试基于该库连接的 C 库来推断 ELF 库(比如 libc5 或 libc6/glibc)的类型。

一些现有的库没有包含足够的信息来推断其类型。
因此, /etc/ld.so.conf 文件格式允许指定期望的类型。
这只在这些 ELF 库不能被解决的情况下使用。

ldconfig 期望的符号链接有某种特定的形式,比如:

    libfoo.so -> libfoo.so.1 -> libfoo.so.1.12

其中,中间的文件 libfoo.so.1 是库的 SONAME 。

如果不遵循这种格式可能会导致升级后的兼容性问题。

ldd

描述:
    ldd调用标准动态连接器(见 ld.so(8)),并且将环境变量 LD_TRACE_LODADED_OBJECTS 为 1 。
    这会让动态连接器检查程序的动态依赖,并且寻找(根据 ld.so(8) 描述的规则)
    和加载满足这些依赖的目标。对于每一条依赖,
    ldd 显示匹配的目标的位置和其载入处的16进制地址。
    (linux-vdso和ld-linux共享依赖是特殊的;见vdso(7)和ld.so(8))

安全性:
    注意,在某些情况下,一些版本的ldd可能会尝试通过直接运行程序(可能导致程序中的ELF解释器
    或程序本身的运行)来获取依赖信息。

    因此,永远不要在不受信任的可执行文件上使用ldd,因为会导致随意代码的运行。更安全替代方法为:
        $ objdump -p /path/to/program | grep NEEDED
    注意,这种替代方法只会显示该可执行文件的直接依赖,而ldd显示该可执行文件的整个依赖树。

解释ldd的输出:
$ ldd -v libibsupport_real.so 
./libibsupport_real.so: /usr/lib64/libibverbs.so.1: version `IBVERBS_1.8' not found (required by ./libibsupport_real.so)
    linux-vdso.so.1 =>  (0x00002ad3e49e3000)
    libibverbs.so.1 => /usr/lib64/libibverbs.so.1 (0x00002ad3e589b000)
    ...

    Version information:
    ./libibsupport_real.so:
            libgcc_s.so.1 (GCC_3.0) => /usr/lib64/libgcc_s.so.1
            libibverbs.so.1 (IBVERBS_1.8) => not found
            libibverbs.so.1 (IBVERBS_1.1) => /usr/lib64/libibverbs.so.1
            ...
    /usr/lib64/libnl-3.so.200:
                libm.so.6 (GLIBC_2.2.5) => /usr/lib64/libm.so.6
                ...
在"Version information"中,"libgcc_s.so.1 (GCC_3.0) => /usr/lib64/libgcc_s.so.1"表示:
    "libgcc_s.so.1"指定一个shared library的名字(libgcc_s.so.1是GCC runtime library的一部分),该shared library为"./libibsupport_real.so"所依赖;
    "(GCC_3.0)"表明"libgcc_s.so.1"需要3.0版本及以上的GNU Compiler Collection (GCC);
    "=>"指出满足依赖的shared library;
    "/usr/lib64/libgcc_s.so.1"是满足要求的shared library的路径。

ldd output说明

sprof

参考:stackoverflows

objdump

[-p|--private-headers]
[-x|--all-headers]

readelf

[-d|--dynamic]

文件

/lib/ld.so
    Run-time linker/loader.
/etc/ld.so.conf
    File containing a list of directories, one per line, in which to search for libraries.
/etc/ld.so.cache
    File containing an ordered list of libraries found in the directories specified in /etc/ld.so.conf, as well as those found in the trusted directories.

The trusted directories:
    /lib
    /lib64
    /usr/lib
    /usr/lib64

ld.so

名字
    ld.so, ld-linux.so - 动态连接/加载器。

简介
    动态连接器可以被间接运行或直接运行。
    间接运行:
        运行某些动态连接程序或共享库。在这种情况下,不能向动态连接器传递命令行选项;并且在ELF情况下,存储在程序的.interp section中的动态连接器被执行。
    直接运行:
        /lib/ld-linux.so.* [OPTIONS] [PRAGRAM [ARGUMENTS]]

描述
    ld.so 和 ld-linux.so* 寻找和加载程序所需的共享对象(共享库),准备程序的运行,然后运行它。

    如果在编译期没有向 ld(1) 指定 -static 选项,则Linux二进制文件需要动态连接(在运行时连接)。

SONAME

参考:man ldconfig
参考:SONAME Wiki

GNU linker使用 -hname 或 -soname=name 来指定该库的library name field。
在内部,linker会创建一个 DT_SONAME field并且用 name 来填充它。

  1. 指定SONAME:
1
2
3
4
#Use ld:
$ ld -shared -soname libexample.so.1 -o libexample.so.1.2.3 file1.o file2.o
#Use gcc or g++:
$ gcc -shared -Wl,-soname,libexample.so.1 -o libexample.so.1.2.3 file1.o file2.o
  1. 安装时创建软链接:
1
2
ln -s libexample.so.1.2.3 libexample.so.1
ln -s libexample.so.1 libexample.so
  1. 查看SONAME:
1
2
3
4
5
6
#Use objdump
$ objdump -p libexample.so.1.3 | grep SONAME
SONAME libexample.so.1
#Use readelf
$ readelf -d libexample.so | grep SONAME
0x000000000000000e (SONAME) Library soname: [libexample.so.1]

符号版本控制(symbol versioning)

定制函数版本:

  1. example.c中定义函数my_printf
1
2
3
4
5
#include <stdio.h>

void my_printf(const char* format) {
printf("%s", format);
}
  1. version_script.txt中定义函数的版本为VERSION_1
1
2
3
4
5
6
VERSION_1 {
global:
my_printf;
local:
*;
};
  1. 编译时指定version_script.txt为version-script:
1
gcc -shared -Wl,--version-script=version_script.txt -o libexample.so example.o

其中,-Wl,引出连接器选项。

  1. 查看:
    1. 使用readelf -sW libexample.so查看函数my_printf的版本号(”VERSION_1”):
1
2
3
4
5
6
7
8
9
10
Symbol table '.dynsym' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (3)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
5: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (3)
6: 0000000000001105 39 FUNC GLOBAL DEFAULT 13 my_printf@@VERSION_1
7: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS VERSION_1
  1. 使用objdump -T libexample.so
  2. 使用nm -D libexample.so

实现同一个函数有多个版本

  1. 编写源代码
1
2
3
4
5
6
7
8
9
#include <stdio.h>

void foo() {
printf("foo version 1.0\n");
}

void foo_v2() {
printf("foo_v2 version 2.0\n");
}
  1. 编写版本脚本

例如,version_script.map文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
VERS_1.0 {
global: # global表示符号全局可见;默认为局部(也可能通过local显式声明为局部),不会被导出
foo;
} VERS_1.1;

VERS_1.1 {
global:
foo_v2;
foo; # VER_1.1继承自VER_1.0
# 1. foo在VER_1.0中已经声明为了global,不需要再次声明global
# 2. 当然可以再次显式声明为global,但是这不是推荐的做法
# 3. 假设foo在VER_1.0中不是global,此处声明为global,
# 会将原本不是global的foo变得全局可见
# 4. 我们其实无需再次导出foo,因为除非我们显式隐藏(使用local关键字),foo就在
# VER_1.1是和VER_1.0中的可见性保持一致
# 5. 如果希望在VER_1.1中的foo与VER_1.0有不同的行为,那么需要再次将其导出
} VERS_2.0;

VERS_2.0 {
global:
foo_v2
};

VER_1.1继承了VER_1.0的全部符号,同时VER_1.1可以增加或修改符号。

VER_1.0:第一个版本,导出foo符号;
VER_1.1:第二个版本,引入foo_v2,并重新导出foo;

程序依赖的库版本

有如下简单代码:

1
2
3
4
// file: main.cc
int main() {
return 0;
}

正常编译:

1
g++ main.cc -o a.out

查看符号:

1
strings a.out

你会发现其中有一些版本信息:

1
2
3
4
...
GLIBC_2.2.5
GLIBC_2.34
...

这是因为编译器和链接器生成库或可执行文件时,会根据系统上安装的glibc版本(因为库和可执行文件依赖glibc),
为库或可执行文件的函数符号附加上特定的版本信息。
为了保证向下兼容,glibc通过符号控制保留了多个版本的符号,支持老版本的软件能在较新的系统上运行。
例如,如果某个函数在glibc2.2.5中引入,它可以继续保留在之后的版本中,但符号标记为GLIBC_2.2.5。

ELF

ELF - Executable and Linking Format.
ELF描述了normal executable files、relocatable object files、core files和shared objects的格式。

参考:man elf, 博客:elf介绍

链接名

参考:程序员的自我修养, man ld

GCC的提供了不同的方法指定链接的共享库:

  • l<link_name>参数

    指定需要链接的共享库lib.so

  • l:<filename>参数

    通过文件名指定共享库,参考LD手册

  • 全路径指定

  • Wl,-static参数

    指定查找静态库,通过-Wl,-Bdynamic恢复成动态库查找

链接器选项

参考:man ld

ld - The GNU linker

选项:

-rpath=dir
    添加一个目录到运行时库搜寻路径中。

gcc通过 -Wl 前缀指定链接器选项,例如:

 gcc -Wl,--start-group foo.o bar.o -Wl,--end-group
 gcc -Wl,-rpath,'$ORIGIN/../lib'

-rpath-link
    用于在链接时指定额外的共享库搜索路径。在编译/链接阶段,告诉链接器去哪里找间接依赖的共享库。只影响构建过程,不影响运行时行为。
    当你链接一个共享库(比如 libfoo.so),而这个库又依赖其他共享库(比如 libbar.so),如果这些依赖库不在标准路径中,链接器可能找不到它们。这时就可以用 -rpath-link 来告诉链接器去哪里找这些“间接依赖”的库。
选项 用途 生效时间
-L 添加库搜索路径 链接时
-rpath-link 添加间接依赖库的搜索路径 链接时
-Wl,-rpath 将路径写入可执行文件供运行时使用 运行时
LD_LIBRARY_PATH 环境变量,指定运行时库路径 运行时

Tips

  1. How to find out the dynamic libraries are loaded when running an executable?

Answer:

  • ldd /path/to/program
  • objdump -p /path/to/program | grep NEEDED
  • lddtree (from pax-utils) or readelf -d /bin/ls | grep 'NEEDED'
  • lsof -p PID | grep mem
1
2
3
4
$ pidof nginx
6920 6919

$ lsof -p 6919 | grep mem
  • strace -e trace=open myprogram

ldd and lsof show the libraries loaded either directly or at a given moment. They do not account for libraries loaded via dlopen (or discarded by dlclose). You can get a better picture of this using strace.

  • pmap <pid> -p

Notice

  1. 生成so时,链接器不会寻找其so依赖;executable会寻找so的依赖关系。换句话说,即使so生成过程不报错,但是executable生成时可能会报错。

  2. 生成so时,如果引用了其他 so ,只要 include 其他 so 的头文件,引用头文件中的符号时,就能编译通过。但是这样生成的 so 没有加上对应符号的依赖。
    此时用 readelf -s 查看对应的符号,其 type 为 NOTYPE 。

    1
    2
    3
    4
    5
    $ readelf -sW libb.so

    Symbol table '.dynsym' contains 14 entries:
    Num: Value Size Type Bind Vis Ndx Name
    6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1fv
  3. 如果加上对应 -la 选项,就会从 liba.so 中寻找依赖的符号,如果找到,则添加进入 so 的依赖项中。当运行时则会加载 liba.so ,否则运行时不会加载。
    用 readelf -s 查看对应的符号,其 type 不再时 NOTYPE ,而是 FUNC (如果该符号是一个函数的话)。

    1
    6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _Z1fv

    如果没有找到符号,或者说 liba.so 中根本没有实现这个函数,那么其 type 依然是 NOTYPE ,此时也不会报错。
    注意:如果只是 include 头文件,也就说只有某个符号的声明,并没有其定义或引用它,那么 so 中不会生成其信息。

示例

运行时错误:”/path/to/a.out: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.30’ not found (required by /path/to/libxxx.so)”

解释:libxxx.so 引用了一个 GLIBCXX_3.4.30 的符号(可能是全局变量或函数),但是在系统的 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 中没有这个版本的符号。

1
2
3
4
5
6
7
8
9
10
$ strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep 'GLIBCXX_3.4.'
GLIBCXX_3.4.1
...
GLIBCXX_3.4.27
GLIBCXX_3.4.28
# 没有 GLIBCXX_3.4.30

$ strings /path/to/libxxx.so | grep 'GLIBCXX_3.4.30' | c++filt
GLIBCXX_3.4.30
std::condition_variable::wait(std::unique_lock<std::mutex>&)@@GLIBCXX_3.4.30

这往往是因为原始的版本中,libxxx.so 是在一台机器中编译的,此时将该机器的 GLIBCXX_3.4.30 符号编译到其中了。但是运行时的机器是另一台,该机器上
不存在 GLIBCXX_3.4.30 版本,所以运行时报错。

或者,因为环境变量的配置错误,编译时和运行时引用的 libstdc++.so.6 不是同一个,这也会导致运行时可能找不到编译时的 GLIBCXX_3.4.30 版本。

可以在源文件中使用指定的版本(使用运行时的版本,比如 GLIBCXX_3.4.11 ):

  1. 查找版本:
1
2
3
4
5
6
7
$ objdump -T /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep condition_variable | c++filt
00000000000d52d0 g DF .text 000000000000000c GLIBCXX_3.4.30 std::condition_variable::wait(std::unique_lock<std::mutex>&)
00000000000ac730 g DF .text 000000000000001c (GLIBCXX_3.4.11) std::condition_variable::wait(std::unique_lock<std::mutex>&)

$ objdump -T /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep condition_variable
00000000000d52d0 g DF .text 000000000000000c GLIBCXX_3.4.30 _ZNSt18condition_variable4waitERSt11unique_lockISt5mutexE
00000000000ac730 g DF .text 000000000000001c (GLIBCXX_3.4.11) _ZNSt18condition_variable4waitERSt11unique_lockISt5mutexE

可以确定符号为 _ZNSt18condition_variable4waitERSt11unique_lockISt5mutexE ,版本为 GLIBCXX_3.4.11

在源文件中指定版本

symverview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/** 指定特定版本的符号 START */

#ifdef __cplusplus
extern "C" {
#endif /** __cplusplus */

__asm__ (".symver _ZNSt18condition_variable4waitERSt11unique_lockISt5mutexE, _ZNSt18condition_variable4waitERSt11unique_lockISt5mutexE@GLIBCXX_3.4.11");

#ifdef __cplusplus
}
#endif /** __cplusplus */

/** 指定特定版本的符号 END */

// 之后就可以使用 std::condition_variable::wait(std::unique_lock<std::mutex>&) 就是 GLIBCXX_3.4.11 的
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>

std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;

void worker_thread()
{
// wait until main() sends data
std::unique_lock lk(m);
cv.wait(lk, []{ return ready; });

// after the wait, we own the lock
std::cout << "Worker thread is processing data\n";
data += " after processing";

// send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";

// manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}

int main()
{
std::thread worker(worker_thread);

data = "Example data";
// send data to the worker thread
{
std::lock_guard lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();

// wait for the worker
{
std::unique_lock lk(m);
cv.wait(lk, []{ return processed; });
}
std::cout << "Back in main(), data = " << data << '\n';

worker.join();
}

编译:

1
g++ symver.cpp

验证符号版本:

1
2
3
$ strings a.out | grep condition_variable | c++filt
std::condition_variable::wait(std::unique_lock<std::mutex>&)
std::condition_variable::wait(std::unique_lock<std::mutex>&)@GLIBCXX_3.4.11

手动加载so

示例代码:

dlopen.cppview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

typedef void (*func_t)(); // 定义函数指针类型

int main() {
// 打开 .so 文件
void *handle = dlopen("your_library.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}

// 清除任何现有的错误
dlerror();

// 读取符号
// void (*func)(); // 方法一:声明并定义函数指针
func_t func; // 方法二:定义函数指针
// 以下四种方法/写法都可以用于将 dlsym 返回的 void* 赋值给函数指针
// (1) *(void **) (&func) = dlsym(handle, "your_function");
// 解释:取函数指针 func 的地址,将其转为 void**,然后将 dlsym 返回的 void* 赋值给它解引用后的变量(即 func)
// (2) func = (void (*)())dlsym(handle, "your_function");
// (3) func = reinterpret_cast<func_t>(dlsym(handle, "your_function"));
// (4) 如下
func = (func_t)dlsym(handle, "your_function");
char *error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}

// 调用符号
func();

// 关闭 .so 文件
dlclose(handle);
return 0;
}

basic_string::_M_construct null not valid

C++有一个缺陷,请看以下代码:

1
2
3
4
5
6
7
8
//cpp defeat: basic_string::_M_construct null not valid
void fun(string s) {
cout << "fun" << endl;
}

int main() {
fun(0); // Run-time error: basic_string::_M_construct null not valid
}

其中,fun(0)0会被视为const char*类型,也就是nullptr,所以在编译期可以通过。
但是运行期会触发string对象的构造错误“basic_string::_M_construct null not valid”。

隐蔽一点的代码:

1
2
3
4
5
6
7
8
9
10
11
char * get_a_string() {
return nullptr;
}

int main() {
// Attention: Alaways take care that a parameter to a string should not be NULL!
fun(get_a_string()); // Run-time error: basic_string::_M_construct null not valid
// Better code
char * str = get_a_string();
fun(str != NULL? str : "");
}

Unit testing framework

CppTest

Memory check

valgrind
参考:
博客
文档
可以使用PostScript查看图形化结果

AddressSanitizer

Performance analyzer

Oracle Developer Studio:

Performance Analyzer手册

Thread Analyzer

gprof: 使用方法

timer

1
$ /usr/bin/time -p ls

Or,

1
$ time ls

其中(参考链接),

1
2
3
$ type -a time
time is a shell keyword
time is /usr/bin/time

CPU时间

Function’s CPU time:

* Inclusive time: total cpu time, include all functions it calls.
* Exclusive time: only the time used by the function itself, exclusive all its children.

Refer to [here](https://stackoverflow.com/questions/15760447/what-is-the-meaning-of-incl-cpu-time-excl-cpu-time-incl-real-cpu-time-excl-re/74426370).
  1. Wall time: total time the process used, containing IO time.

  2. CPU usage (CPU利用率) = CPU time / Wall time.

  3. real/user/system time

    • Real is wall clock time - time from start to finish of the call. This is all elapsed time including time slices used by other processes and time the process spends blocked (for example if it is waiting for I/O to complete).
    • User is the amount of CPU time spent in user-mode code (outside the kernel) within the process. This is only actual CPU time used in executing the process. Other processes and time the process spends blocked do not count towards this figure.
    • Sys is the amount of CPU time spent in the kernel within the process. This means executing CPU time spent in system calls within the kernel, as opposed to library code, which is still running in user-space. Like ‘user’, this is only CPU time used by the process. See below for a brief description of kernel mode (also known as ‘supervisor’ mode) and the system call mechanism.

    Refer to here.

  4. CPU 时间可能大于墙上时间:

    这是因为 CPU 时间是所有 CPU 核的运行时间的累加和,墙上时间则是实际的时间。此时 CPU 利用率大于 100%. (这是自己的理解)

  5. TODO: Is CPU time in flame graph sum of all the CPU time? Or is it the wall time when CPU works?

debug tool

gdb

1
2
3
4
(gdb) breakpoint exit
(gdb) breakpoint _exit
(gdb) breakpoint atexit
(gdb) breakpoint abort

Enable coredump: how to do

1
ulimit -c unlimited

Where is the core dumped file:

1
grep 'kernel.core_pattern' /etc/sysctl.conf

strace

Example:

1
strace -f -o strace.log -tt -y -yy -e trace=desc,process,network

Refer to here

-e trace=ipc – communication between processes (IPC)
-e trace=memory – memory syscalls
-e trace=network – network syscalls
-e trace=process – process calls (like fork, exec)
-e trace=signal – process signal handling (like HUP, exit)
-e trace=file – file related syscalls
-e trace=desc – all file descriptor related system calls

DDT

DDT