gitignore不生效
参考:链接。
file mode 10064的含义
参考博客
environ
- 用户环境,一个全局变量。头文件 <unistd.h>
。可以通过 man environ
查看手册。
1 | #include <unistd.h> |
getenv
- 获取环境变量。头文件 <stdlib.h>
。
setenv
- 设置环境变量。头文件 stdlib.h
。
sched_setaffinity
参考manpage.
affinity 表示 CPU 亲缘性:即与哪个更亲,更愿意属于哪一个。
https://blog.csdn.net/itjinks/article/details/40477779
CPU affinity 是一种调度属性(scheduler property), 它可以将一个进程”绑定” 到一个或一组CPU上.
参考链接。
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
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 |
1 | set max-value-size bytes |
打印字符长度限制
gdb默认设置打印字符串的长度为200;更改打印最大长度:set print elements
gdb命令:gcore
。
WSL指Windows虚拟机。
解决方法:
1 | sudo add-apt-repository ppa:ubuntu-support-team/gdb |
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?
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 | (gdb) info inferiors |
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 | (gdb) info inferiors |
You need to enable logging:
1 | (gdb) set logging on |
记录输入的命令:
1 | (gdb) set trace-commands on |
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 |
参考:官方文档。
参考:
属性列举:
format (archetype, string-index, first-to-check)
format 属性,指定函数采用 printf、scanf、strftime 或 strfmon 风格的参数,
这些参数应根据格式字符串(format string)进行类型检查(type-checked)。
类型检查发生在编译期。
举例:
1 | extern int |
archetype
决定format string应该如何解释。printf
、scanf
、strftime
或strfmon
(也可以使用__printf__
、__scanf__
、__strftime__
或__strfmon__
)。string-index
指定哪个参数是format string(从1开始)。first-to-check
指定format string对应的第一个参数的序号。vprintf
),该参数指定为0
。在这种情况下,编译器仅检查format string的一致性。对于strftime
格式,该参数必须为0
。-save-temps
: 可以保留所有中间文件,例如预编译文件、汇编文件、目标文件等。ulimit
1 | #查看配置 |
ulimit
只对当前终端有效。
以下两种方法对所有用户和终端有效:
/etc/security/limits.conf
中设置(redhat衍生系linux)。/etc/profile
中的这一行: 1 | # No core files by default |
见链接。
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 | $ cat /proc/sys/kernel/core_pattern |
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 | %c - Core file size soft resource limit of crashing process (since Linux 2.6.24). |
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.
见链接。
/proc/sys/kernel/core_uses_pid可以控制产生的core文件的文件名中是否添加pid作为扩展,如果添加则文件内容为1,否则为0;
/proc/sys/kernel/core_pattern可以设置格式化的core文件保存位置或文件名:
1 | $ cat /proc/sys/kernel/core_pattern |
你可以用下列方式来完成:
1 | #查看所有sysctl所有变量的值。 |
这些操作一旦计算机重启,则会丢失,如果你想持久化这些操作,可以在 /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)
参考链接。
参考博客
1 | gcc -E test.c -o test.i |
编译
编译是将高级语言(例如C、C++、Jave等)代码转换成机器码的过程。
编译可以分成多个阶段,包括词法分析、语法分析、语义分析、优化和代码生成。
编译器首先将源代码转换一种中间表示(通常是汇编代码或字节码),然后再将其转换为目标机器的机器代码。
经过编译的代码通常是二进制的,可以直接在目标机器上执行。
先生成汇编代码:
1 | gcc -S test.i -o test.s |
汇编
将汇编代码转成机器码。
1 | gcc -c test.s -o test.o |
链接
该目标文件与其他目标文件、库文件、启动文件等链接起来生成可执行文件。
1 | gcc test.o -o test |
Note: 可以使用 -save-temps
选项以保留编译过程中的所有中间文件:
1 | gcc -save-temps test.c |
Name mangling (C++ only)
: 名称修饰,也称为名称重整、名称改编。参见链接1、2。
参考: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的路径。
sprof
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二进制文件需要动态连接(在运行时连接)。
参考:man ldconfig
参考:SONAME Wiki
GNU linker使用 -hname 或 -soname=name 来指定该库的library name field。
在内部,linker会创建一个 DT_SONAME field并且用 name 来填充它。
1 | #Use ld: |
1 | ln -s libexample.so.1.2.3 libexample.so.1 |
1 | #Use objdump |
my_printf
:1 | #include <stdio.h> |
VERSION_1
:1 | VERSION_1 { |
version_script.txt
为version-script:1 | gcc -shared -Wl,--version-script=version_script.txt -o libexample.so example.o |
其中,-Wl,
引出连接器选项。
readelf -sW libexample.so
查看函数my_printf
的版本号(”VERSION_1”):1 | Symbol table '.dynsym' contains 8 entries: |
objdump -T libexample.so
nm -D libexample.so
1 | #include <stdio.h> |
例如,version_script.map文件如下:
1 | VERS_1.0 { |
VER_1.1继承了VER_1.0的全部符号,同时VER_1.1可以增加或修改符号。
VER_1.0:第一个版本,导出foo符号;
VER_1.1:第二个版本,引入foo_v2,并重新导出foo;
有如下简单代码:
1 | // file: main.cc |
正常编译:
1 | g++ main.cc -o a.out |
查看符号:
1 | strings a.out |
你会发现其中有一些版本信息:
1 | ... |
这是因为编译器和链接器生成库或可执行文件时,会根据系统上安装的glibc版本(因为库和可执行文件依赖glibc),
为库或可执行文件的函数符号附加上特定的版本信息。
为了保证向下兼容,glibc通过符号控制保留了多个版本的符号,支持老版本的软件能在较新的系统上运行。
例如,如果某个函数在glibc2.2.5中引入,它可以继续保留在之后的版本中,但符号标记为GLIBC_2.2.5。
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
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 | 环境变量,指定运行时库路径 | 运行时 |
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 | $ pidof nginx |
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
生成so时,链接器不会寻找其so依赖;executable会寻找so的依赖关系。换句话说,即使so生成过程不报错,但是executable生成时可能会报错。
生成so时,如果引用了其他 so ,只要 include 其他 so 的头文件,引用头文件中的符号时,就能编译通过。但是这样生成的 so 没有加上对应符号的依赖。
此时用 readelf -s 查看对应的符号,其 type 为 NOTYPE 。
1 | $ readelf -sW libb.so |
如果加上对应 -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 中不会生成其信息。
解释:libxxx.so
引用了一个 GLIBCXX_3.4.30
的符号(可能是全局变量或函数),但是在系统的 /usr/lib/x86_64-linux-gnu/libstdc++.so.6
中没有这个版本的符号。
1 | $ strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep 'GLIBCXX_3.4.' |
这往往是因为原始的版本中,libxxx.so
是在一台机器中编译的,此时将该机器的 GLIBCXX_3.4.30
符号编译到其中了。但是运行时的机器是另一台,该机器上
不存在 GLIBCXX_3.4.30
版本,所以运行时报错。
或者,因为环境变量的配置错误,编译时和运行时引用的 libstdc++.so.6
不是同一个,这也会导致运行时可能找不到编译时的 GLIBCXX_3.4.30
版本。
可以在源文件中使用指定的版本(使用运行时的版本,比如 GLIBCXX_3.4.11
):
1 | $ objdump -T /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep condition_variable | c++filt |
可以确定符号为 _ZNSt18condition_variable4waitERSt11unique_lockISt5mutexE
,版本为 GLIBCXX_3.4.11
。
在源文件中指定版本
1 | /** 指定特定版本的符号 START */ |
编译:
1 | g++ symver.cpp |
验证符号版本:
1 | $ strings a.out | grep condition_variable | c++filt |
示例代码:
1 | #include <dlfcn.h> |
C++有一个缺陷,请看以下代码:
1 | //cpp defeat: basic_string::_M_construct null not valid |
其中,fun(0)
的0
会被视为const char*
类型,也就是nullptr
,所以在编译期可以通过。
但是运行期会触发string
对象的构造错误“basic_string::_M_construct null not valid”。
隐蔽一点的代码:
1 | char * get_a_string() { |
valgrind
参考:
博客
文档
可以使用PostScript查看图形化结果
Oracle Developer Studio:
1 | $ /usr/bin/time -p ls |
Or,
1 | $ time ls |
其中(参考链接),
1 | $ type -a time |
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).
Wall time: total time the process used, containing IO time.
CPU usage (CPU利用率) = CPU time / Wall time.
real/user/system time
Refer to here.
CPU 时间可能大于墙上时间:
这是因为 CPU 时间是所有 CPU 核的运行时间的累加和,墙上时间则是实际的时间。此时 CPU 利用率大于 100%. (这是自己的理解)
TODO: Is CPU time in flame graph sum of all the CPU time? Or is it the wall time when CPU works?
1 | (gdb) breakpoint exit |
Enable coredump: how to do
1 | ulimit -c unlimited |
Where is the core dumped file:
1 | grep 'kernel.core_pattern' /etc/sysctl.conf |
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