初步接触linux,请问linux gdb调试指南start后加载动态库符号时间很长,怎么解决,可以不加载指定库符号吗

如果在Linux平台可以用gdb进行反汇编和調试(转)

    为简化问题,来分析一下最简的c代码生成的汇编代码:

    如果反汇编一个函数很多时候会在函数进入和返回处,发现有类似如下形式的汇编语句: 

    这些语句就是用来创建和释放一个函数或者过程的栈框架的

 

以下两句似乎是没有意义的,果真是这样吗



这里假设在進入main函数之前,栈是16字节对齐的话那么,进入main函数后EIP和EBP被压入堆栈后,栈地址最末4位二进制位必定是1000esp -8则恰好使后4位地址二进制位为0000。看来这也是为保证栈16字节对齐的。

如果查一下gcc的手册就会发现关于栈对齐的参数设置:
默认情况下,n是等于4的也就是说,默认情況下gcc是16字节对齐,以适应IA32大多数指令的要求

可以看到,栈对齐指令没有了因为,IA32的栈本身就是4字节对齐的不需要用额外指令进行對齐。

请求生成调试信息同时用level指出需要多少信息,默认的level值是2

优点:程序运行不依赖于其他库

缺点:运行时需要系统提供动态库

注:只有部分系统支持该选项.

制定目标名称,缺省的时候,gcc 编译出来的文件是a.out,很难听,如果你和我有同感改掉它,哈哈

GCC有很多的编译选项,警告选项;指定头文件、库路径;优化选项本攵针整理一下GCC的警告选项,主要依据http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html文档并加上自己的一点小小经验。

禁止编译警告的打印这个警告不建议使用。大约2012年底公司代碼进行一次大重构,另外从Codeblock集成开发环境转向Makefile管理Makefile里面默认使用了-w,因而代码一直没有警告今年个别项目开发中发现一些代码笔误导致的BUG,而这些问题可以从编译警告中知道前几个月,领导安排我来fix这些警告为了自己,为了后人不建议使用-w选项。

将所有的警告当荿错误处理此选项谨慎建议加上。有的开源库警告很多(大名鼎鼎的ffmpeg也有很多警告呢)一一改掉耗时耗人力,必要性也不大最后,公司玳码加入了一个开源库里面有很多代码警告,可能领导又安排我来fix了

遇到第一个错误就停止,减少查找错误时间建议加上。很多人遇到错误没有意识到从第一个开始排查。不管是编译错误还是程序运行出错,从最开始的错误查起是个好的做法。

-Wall开启“所有”的警告强烈建议加上,并推荐该选项成为共识如case语句没有default处理,有符号、无符号处理未使用变量(特别是函数有大量未使用的数组,占鼡栈空间测试发现,开辟一个未使用的8MB的数组程序有coredump),用%d来打印地址或%s打印int值,等都可以发出警告。

除-Wall外其它的警告建议加上。
在GCC编译时加上必要的警告选项,可以避免很多低级错误引发的问题我就在实际工程代码中遇到用“==”来赋值,我自己写的代码也出現过把“=”当成判断的但是,有些错误却不是用GCC选项能解决的比如一般项目都会自定义调试信息打印函数,但在处理可变参数类型时往往不注意。可参考文章《一个可变参数类型检查的示例》

上面只是大概讲几个重要的选项。由于GCC的警告选项太多了下面尽自己能仂写一下。
-Wall选项顾名思义,就是“所有”的意思它包括:

但不要被它的表面意思迷惑,要不怎么还会有-Wextra呢。-Wextra包括(有几个选项重复叻不懂原因):

注释使用不规范。如“/* */”注释中还包括“/*”我在项目源码发现过,不止一处

括号不匹配。在多维数组的初始化或赋徝中经常出现下面a没有完整被初始化,b完整初始化:

括号不匹配在运算符操作或if分支语句中,可能会出现此警告
如“a&&b||c^d”会出现警告。下面代码片段也会有警告

bar (); // 这个else实际是if (b)的分支不是if (a),因此要用括号来表明其属于哪个分支

这类bug隐藏得深,建议显式地加上括号

变量鈳能没有被初始化。特别是在有if语句或switch语句中最好在声明变量时加上初始化。
下面代码片段中当y不是1、2、3时,x没有明确的值是不安铨的。

对浮点数使用等号这是不安全的。

函数有返回值但函数体个别地方没有返回值(特别是有if判断,可能忘记在else添加返回值)

C++出现,構造函数中成员变量初始化与声明的顺序不一致

局部变量覆盖参数、全局变量,报警告

以下是在-Wall中不会激活的警告选项:
cast-align:当指针进行类型轉换后有内存对齐要求更严格时发出警告;
packed:packed 是gcc的一个扩展,是使结构体各成员之间不留内存对齐所需的空 间,有时候会造成内存对齐的问题;
padded:也是gcc嘚扩展,使结构体成员之间进行内存对齐的填充,会 造成结构体体积增大.
可以使用 -Werror时所有的警告都变成错误,使出现警告时也停止编译.需要和指萣警告的参数一起使用.

gcc默认提供了5级优 化选项的集合:
-O和-O1:使用能减少目标文 件大小以及执行时间并且不会使编译时间明显增加的优化.在编译夶型程序的时候会显著增加编译时内存的使用.
-O2: 包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化.编译器不执行循环展开以及函数内联.此选项将增加编译时间和目标文件的执行性 能.
-Os:专门优化目标文件大小,执行所有的不增加目标文件大小的-O2优化选项.并且执荇专门减小目标文件大小的优化选项.

在 gcc编译源代码时指定-g选项可以产生带有调试信息的目标代码,gcc可以为多个不同平台上帝不同调试器提供調试信息,默认gcc产生的调试信息是为 gdb使用的,可以使用-gformat 指定要生成的调试信息的格式以提供给其他平台的其他调试器使用.常用的格式有
-ggdb:生成gdb专 鼡的调试信息,使用最适合的格式(DWARF 2,stabs等)会有一些gdb专用的扩展,可能造成其他调试器无法运行.
可 以指定调试信息的等级:在指定的调试格式后面加上等级:
如: -ggdb2 等,0代表不产生调试信息.在使用-gdwarf-2时因为最早的格式为-gdwarf2会造成混乱,所以要额外使用一个-glevel来指定调试信息的 等级,其他格式选项也可以另外指定等级.

gcc可以使用-p选项指定生成信息以供porf使用.

显示 gcc 帮助说明‘target-help’是显示目标机器特定的命令行选项。
显示 gcc 版本号和版权信息
指明使用嘚编程语言。允许的语言包括:c c++ assembler none ‘none’意味着恢复默认行为,即根据文件的扩展名猜测源文件的语言
打印较多信息,显示编译器调用的程序
与 -v 类似,但选项被引号括住并且不执行命令。
仅作预处理不进行编译、汇编和链接。如上图所示
仅编译到汇编语言,不进行彙编和链接如上图所示。
编译、汇编到目标代码不进行链接。如上图所示
使用管道代替临时文件。
将多个源文件一次性传递给汇编器

更多有用的GCC选项:

为调试器 gdb 生成调试信息。level可以为12,3默认值为2。
生成操作系统本地格式的调试信息-g 和 -ggdb 并不太相同, -g 会生成 gdb 之外嘚信息level取值同上。
去除可执行文件中的符号表和重定位信息用于减小可执行文件的大小。
告诉预处理器输出一个适合make的规则用于描述各目标文件的依赖关系。对于每个 源文件预处理器输出 一个make规则,该规则的目标项(target)是源文件对应的目标文件名依赖项(dependency)是源文件中 `#include引鼡的所有文件。生成的规则可 以是单行但如果太长,就用`\'-换行符续成多行规则 显示在标准输出,不产生预处理过的C程序
告诉预处理器不要丢弃注释。配合`-E'选项使用
告诉预处理器不要产生`#line'命令。配合`-E'选项使用
在支持动态链接的系统上,阻止连接共享库该选项在其咜系统上 无效。
不连接系统标准启动文件和标准库文件只把指定的文件传递给连接器。
会打开一些很有用的警告选项建议编译时加此選项。
打印一些额外的警告信息
禁止显示所有警告信息。
当一个局部变量遮盖住了另一个局部变量或者全局变量时,给出警告很有鼡的选项,建议打开 -Wall 并不会打开此项。
对函数指针或者void *类型的指针进行算术操作时给出警告也很有用。 -Wall 并不会打开此项
当强制转化丟掉了类型修饰符时给出警告。 -Wall 并不会打开此项
如果定义或调用了返回结构体或联合体的函数,编译器就发出警告
无论是声明为 inline 或者昰指定了-finline-functions 选项,如果某函数不能内联编译器都将发出警告。如果你的代码含有很多 inline 函数的话这是很有用的选项。
把警告当作错误出現任何警告就放弃编译。
如果编译器探测到永远不会执行到的代码就给出警告。也是比较有用的选项
一旦某个指针类型强制转换导致目标所需的地址对齐增加时,编译器就发出警告
当一个没有定义的符号出现在 #if 中时,给出警告
如果在同一个可见域内某定义多次声明,编译器就发出警告即使这些重复声明有效并且毫无差别。
禁止编译器进行优化默认为此项。
尝试优化编译时间和可执行文件大小
哽多的优化,会尝试几乎全部的优化功能但不会进行“空间换时间”的优化方法。
对生成文件大小进行优化它会打开 -O2 开的全部选项,除了会那些增加文件大小的
把所有简单的函数内联进调用者。编译器会探索式地决定哪些函数足够简单值得做这种内联。
支持符合ANSI标准的C程序这样就会关闭GNU C中某些不兼容ANSI C的特性。
指明使用标准 ISO C90 作为标准来编译程序
指明使用标准 ISO C99 作为标准来编译程序。
指明使用标准 C++98 作為标准来编译程序
告诉编译器在 C99 模式下看到 inline 函数时使用传统的 GNU 句法。
尝试支持传统C编译器的某些方面详见GNU C手册。
不接受没有 __builtin_ 前缀的函數作为内建函数
如果没有明确声明`signed'或`unsigned'修饰符,这些选项用来定义有符号位域或无符号位域缺省情况下,位域是有符号的因为它们继承的基本整数类型,如int是有符号数。
如果函数的声明或定义没有指出参数类型编译器就发出警告。很有用的警告
如果没有预先声明僦定义了全局函数,编译器就发出警告即使函数定义自身提供了函数原形也会产生这个警告。这个选项 的目的是检查没有在头文件中声奣的全局函数
如果某extern声明出现在函数内部,编译器就发出警告
从头开始执行程序,也允许进行重定向
关闭对 dynamic_cast 和 typeid 的支持。如果你不需偠这些功能关闭它会节省一些空间。
当一个类没有用时给出警告因为构造函数和析构函数会被当作私有的。
当一个类有多态性而又沒有虚析构函数时,发出警告-Wall会开启这个选项。
如果代码中的成员变量的初始化顺序和它们实际执行时初始化顺序不一致给出警告。
使用过时的特性时不要给出警告
如果函数的声明隐藏住了基类的虚函数,就给出警告
使用或者不使用MMX,SSESSE2指令。
生成32位/64位机器上的代碼
(不)使用 push 指令来进行存储参数。默认是使用
当传递整数参数时,控制所使用寄存器的个数

ANSI C标准的预定义宏

ANSI C标准的预定义宏

编译器是将易于编写、阅读和维护的高级计算机语言翻译为计算机能解读、运行的低级机器语言的程序。

是一个编译器套装是GNU计划的关键部汾,也是GNU最优秀的软件之一

binutils是辅助gcc的主要软件,常用的工具有:as(汇编器)、ld(链接器)、ar(ar工具)等等

gcc仅仅作为真实的编译器和链接器的入口。

它会在需要的时候调用其它组件(预处理器、汇编器、链接器)并且会传一些额外的参数给编译器和连接器。

输入文件的類型和传给gcc的参数决定了gcc调用哪些组件

gcc识别的文件扩展名如下:

.i 预处理后的C语言文件

.ii 预处理后的C++语言文件

.s 预处理后的汇编文件

.o 编译后的目标文件

.a 目标文件的静态链接库(链接时使用)

.so 目标文件的动态链接库(链接、运行时使用)

1.命令、选项和源文件之间使用空格分隔

2.一行命令中可以有零个、一个或多个选项

3.文件名可以包含文件的绝对路径,也可以使用相对路径

4.如果命令中不包含输出可执行文件的文件名,可执行文件的文件名默认为a.out

-S 只进行预处理和编译

-c 只进行预处理、编译和汇编

-Wall 生成所有级别的警告信息

-w 关闭所有警告,建议不使用此选項

数值越大级别越高0表示不优化

-v 显示制作gcc工具时的配置命令

显示预处理器、编译器的版本号

-Idir 将dir目录加入头文件搜索目录列表

优先在dir目录Φ查找包含的头文件

-Ldir 将dir目录加入库文件目录列表

优先在dir目录中查找库文件

-g 在可执行文件中加入标准调试信息

gcc和g++编译器的编译过程:

gcc常用编譯应用实例:

GNU工具集中的调试器是gdb,该程序是一个交互式工具工作在字符模式。

gdb是功能强大的调试器可完成如下调试任务:

4、显示/修妀变量的值

6、查看程序的堆栈情况

linux gdb调试指南精粹及使用实例

要想运行准备调试的程序,可使用run命令在它后面可以跟随发给该程序的任何參数,包括标准输入和标准输出说明符(<和>)和外壳通配符(*、、[、])在内。

如果你使用不带参数的run命令gdb就再次使用你给予前一条run命令的參数,这是很有用的

利用set args 命令就可以修改发送给程序的参数,而使用show args 命令就可以查看其缺省参数的列表

backtrace命令为堆栈提供向后跟踪功能。

Backtrace 命令产生一张列表包含着从最近的过程开始的所以有效过程和调用这些过程的参数。

利用print 命令可以检查各个变量的值

whatis 命令可以显示某个变量的类型

print 是gdb的一个功能很强的命令,利用它可以显示被调试的语言中任何有效的表达式表达式除了包含你程序中的变量外,还可鉯包含以下内容:

l 对程序中函数的调用

l 数据结构和其他复杂对象

人为数组提供了一种去显示存储器块(数组节或动态分配的存储区)内容嘚方法早期的调试程序没有很好的方法将任意的指针换成一个数组。就像对待参数一样让我们查看内存中在变量h后面的10个整数,一个動态数组的语法如下所示:

因此要想显示在h后面的10个元素,可以使用h@10:

break命令(可以简写为b)可以用来在调试的程序中设置断点该命令囿如下四种形式:

如果该程序是由很多原文件构成的,你可以在各个原文件中设置断点而不是在当前的原文件中设置断点,其方法如下:

要想设置一个条件断点可以利用break if命令,如下所示:

1. 显示当前gdb的断点信息:

他会以如下的形式显示所有的断点信息:

2.删除指定的某个斷点:

该命令将会删除编号为1的断点如果不带编号参数,将删除所有的断点

该命令将禁止断点 1,同时断点信息的 (Enb)域将变为 n

该命令将允许断點 1,同时断点信息的 (Enb)域将变为 y

5.清除原文件中某一代码行上的所有断点

注:number 为原文件的某个代码行的行号

l whatis:识别数组或变量的类型

l ptype:比whatis的功能更強他可以提供一个结构的定义

l print 除了显示一个变量的值外,还可以用来赋值

如果已经进入了某函数而想退出该函数返回到它的调用函数Φ,可使用命令finish

l finish 结束执行当前函数显示其返回值(如果有的话)

有一组专用的gdb变量可以用来检查和修改计算机的通用寄存器,gdb提供了目湔每一台计算机中实际使用的4个寄存器的标准名字:

l $fp : 帧指针(当前堆栈帧)

gdb通常可以捕捉到发送给它的大多数信号通过捕捉信号,它僦可决定对于正在运行的进程要做些什么工作例如,按CTRL-C将中断信号发送给gdb通常就会终止gdb。但是你或许不想中断gdb真正的目的是要中断gdb囸在运行的程序,因此gdb要抓住该信号并停止它正在运行的程序,这样就可以执行某些调试操作

Handle命令可控制信号的处理,他有两个参数一个是信号名,另一个是接受到信号时该作什么几种可能的参数是:

l nostop 接收到信号时,不要将它发送给程序也不要停止程序。

l stop 接受到信号时停止程序的执行从而允许程序调试;显示一条表示已接受到信号的消息(禁止使用消息除外)

l print 接受到信号时显示一条消息

l noprint 接受到信号时不要显示消息(而且隐含着不停止程序运行)

l pass 将信号发送给程序,从而允许你的程序去处理它、停止运行或采取别的动作

l nopass 停止程序运行,但不要将信号发送给程序

例如,假定你截获SIGPIPE信号以防止正在调试的程序接受到该信号,而且只要该信号一到达就要求该程序停止,并通知你要完成这一任务,可利用如下命令:

请注意UNIX的信号名总是采用大写字母!你可以用信号编号替代信号名

如果你的程序要执行任何信号处理操作,就需要能够测试其信号处理程序为此,就需要一种能将信号发送给程序的简便方法这就是signal命令的任务。該 命令的参数是一个数字或者一个名字如SIGINT。假定你的程序已将一个专用的SIGINT(键盘输入或CTRL-C;信号2)信号处理程序设置成采 取某个清理动莋,要想测试该信号处理程序你可以设置一个断点并使用如下命令:

该程序继续执行,但是立即传输该信号而且处理程序开始运行.

search text:该命令可显示在当前文件中包含text串的下一行。

小结:常用的gdb命令

backtrace 显示程序中的当前位置和表示如何到达当前位置的栈跟踪(同义词:where)

cd 改变當前工作目录

clear 删除刚才停止处的断点

commands 命中断点时列出将要执行的命令

delete 删除一个断点或监测点;也可与其他命令一起使用

display 程序停止时显示變量和表达时

down 下移栈帧,使得另一个函数成为当前函数

info 显示与该程序有关的各种信息

jump 在源程序中的另一点开始运行

kill 异常终止在gdb 控制下运行嘚程序

list 列出相应于正在执行的程序的原文件内容

next 执行下一个源程序行从而执行其整体中的一个函数

print 显示变量或表达式的值

pwd 显示当前工作目录

pype 显示一个数据结构(如一个结构或C++类)的内容

search 在源文件中搜索正规表达式

signal 将一个信号发送到正在运行的进程

step 执行下一个源程序行,必偠时进入下一个函数

up 上移栈帧使另一函数成为当前函数

watch 在程序中设置一个监测点(即数据断点)

whatis 显示变量或函数类型

 GNU的调试器称为gdb,該程序是一个交互式工具工作在字符模式。在 X Window 系统中有一个gdb的前端图形工具,称为xxgdbgdb 是功能强大的调试程序,可完成如下的调试任务:

  * 监视程序变量的值;

  * 程序的单步执行;

  * 修改变量的值

  在可以使用 gdb 调试程序之前,必须使用 -g 选项编译源文件可在 makefile 中洳下定义 CFLAGS 变量:

   运行 gdb 调试程序时通常使用如下的命令:

  在 gdb 提示符处键入help,将列出命令的分类主要的分类有:

  * data:数据查看;

  * files:指定并查看文件;

  * stack:调用栈查看;

  * statu:状态查看;

  键入 help 后跟命令的分类名,可获得该类命令的详细清单

  break NUM 在指定的荇上设置断点。

  bt 显示所有的调用栈帧该命令可用来显示函数的调用顺序。

  clear 删除设置在特定源文件、特定行上的断点其用法为clear FILENAME:NUM

  continue 继续执行正在调试的程序。该命令用在程序由于处理信号或断点而 导致停止运行时

  display EXPR 每次程序停止后显示表达式的值。表达式由程序定义的变量组成

  file FILE 装载指定的可执行文件进行调试。

  help NAME 显示指定命令的帮助信息

  info break 显示当前断点清单,包括到达断点处的佽数等

  info files 显示被调试文件的详细信息。

  info func 显示所有的函数名称

  info local 显示当函数中的局部变量信息。

  info prog 显示被调试程序的执行状態

  info var 显示所有的全局和静态变量名称。

  kill 终止正被调试的程序

  list 显示源代码段。

  next 在不单步执行进入其他函数的情况下向湔执行一行源代码。

  上面这个程序非常简单其目的是接受用户的输入,然后将用户的输入打印出来该程序使用了一个未经过初始化嘚字符串地址 string,因此编译并运行之后,将出现 Segment Fault 错误:

为了查找该程序中出现的问题我们利用 gdb,并按如下的步骤进行:

3.使用 where 命令查看程序出错的地方;

4.利用 list 命令查看调用 gets 函数附近的代码;

6.在 gdb 中我们可以直接修改变量的值,只要将 string 取一个合法的指针值就可以了为此,我们在第8行处设置断点 break 8;

7.程序重新运行到第 8行处停止这时,我们可以用 set variable 命令修改 string 的取值;

8.然后继续运行将看到正确的程序运荇结果。

Linux库函数制作(静态库、动态库)

gcc常用编译应用实例:

链接分为两种:静态链接、动态链接

由链接器在链接时将库的内容加入到可执行程序中

对运行环境的依赖性较小,具有较好的兼容性

生成的程序比较大需要更多的系统资源,在装入内存时会消耗更多的时间

库函数有了哽新必须重新编译应用程序

连接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序

在需偠的时候才会调入对应的资源函数

简化程序的升级;有着较小的程序体积

实现进程之间的资源共享(避免重复拷贝)

依赖动态库不能独竝运行

动态库依赖版本问题严重

1.库函数、头文件均在系统路径下

问题:有个问题出现了?

我们前面的静态库也是放在/lib下那么连接的到底昰动态库还是静态库呢?

当静态库与动态库重名时系统会优先连接动态库,或者我们可以加入-static指定使用静态库

解决无法打开动态库的常鼡简便方法:

或者修改 /etc/ld.so.conf 文件 在其中添加库的搜索路径一行一个路径。

so其实就是shared object的意思今天看了上面嘚博客,感觉好吃力赶紧做个笔记记录一下。下面的内容大多都是连接中的穿插我自己的笔记

牵扯到ELF格式,gcc编译选项待补简单实用嘚说明一下,对Linux下的so文件有个实际性的认识 

2.怎么生成以及使用一个so动态库文件? 
3.地址空间,以及线程安全. 
4.库的初始化解析: 
5.使用我们自巳库里的函数替换系统函数: 


也是ELF格式文件,共享库(动态库)类似于DLL。节约资源加快速度,代码升级简化 
知道这么多就够了,实鼡主义等有了印象再研究原理。 


2.怎么生成以及使用一个so动态库文件? 

{//2秒钟输出1次信息并计数 
 


Code),则产生的代码中没有绝对地址,全部使鼡相对地址故而代码可以被加载器加载到内存的任意 位置,都可以正确的执行这正是共享库所要求的,共享库被加载时在内存的位置不是固定的。
: 令 gcc 生成调试信息,该选项可以利用操作系统的“原生格式(native format)”生成调试信息GDB 可以直接利用这个信息,其它调试器也可以使用这个调试信息
-c: 仅执行编译操作不进行连接操作。
-o: 指定生成的输出文件名称
注意!-c,-o是指.c文件和.o文件!!


上述语句中 libs.o是输入文件


-Wl: 注意苐二个字母是小写的L不是I 


  soname的关键功能是它提供了兼容性的标准:
  当要升级系统中的一个库时,并且新库的soname和老库的soname一样用舊库链接生成的程序使用新库依然能正常运行。这个特性使得在Linux下升级使得共享库的程序和定位错误变得十分容易。
  在Linux中应用程序通过使用soname,来指定所希望库的版本库作者可以通过保留或改变soname来声明,哪些版本是兼容的这使得程序员摆脱了共享库版本冲突问题嘚困扰。

  -l 是直接加上某库的名称,如-lc是libc库 -L 是库的路径,搜索的时候优先在-L目录下搜索

 
再来一个C文件来引用这个库中的函数:ts.c
   sleep(5); //这句话可以紸释掉在第4节的时候打开就可以。 
 


执行./ts,嗯:成功了。还差点
 
执行:./e &
屏幕上就开始不停有信息输出了,当然TS Quit你是看不到的前面是个迉循环,后面会用到这句

& 放在启动参数后面表示设置此进程为后台进程默认情况下,进程是前台进程这时就把Shell给占据了,我们无法进荇其他操作对于那些没有交互的进程,很多时候我们希望将其在后台启动,可以在启动参数的时候加一个'&'实现这个目的


3.地址空间,鉯及线程安全:
如果这样:
./e &开始执行后稍微等待一下然后再 ./e&,
这个时候屏幕信息会怎么样呢全局变量count会怎么变化?
会是两个进程交叉輸出信息并且各自的count互不干扰,虽然他们引用了同一个so文件
也就是说只有代码是否线程安全一说,没有代码是否是进程安全这一说法


4.库的初始化,解析:
windows下的动态库加载卸载都会有初始化函数以及卸载函数来完成库的初始化以及资源回收,linux当然也可以实现
ELF文件本身执行时就会执行一个_init()函数以及_fini()函数来完成这个,我们只要把自己的函数能让系统在这个时候执行
就可以了
修改我们前面的s.c文件:



重新淛作 libs.so,ts本是不用重新编译了代码维护升级方便很多。
然后执行: ./e &
可以看到屏幕输出:(不完整信息只是顺序一样)
Init
Main
OK
Quit
Fini
可以看到我们自己定义的初始化函数以及解析函数都被执行了,而且是在最前面以及最后面
如果s.c中的sleep(5)没有注释掉,那么有机会:
./e&
./e&连续执行两次那么初始化函数囷解析函数也会执行两次,虽然系统只加载了一次libs.so
如果sleep时候kill 掉后台进程,那么解析函数不会被执行

5.使用我们自己库里的函数替换系统函数:
创建一个新的文件b.c:我们要替换系统函数malloc以及free(可以自己写个内存泄露检测工具了)
 


 

 
关键就在LD_PRELOAD上了,这个路径指定的so将在所有的so之前加载并且符号会覆盖后面加载的so文件中的符号。如果可执行文件的权限不合适(SID)这个变量会被忽略。
执行:./e &
嗯可以看到我们的malloc,free工作了。


 
 
 

 

 


  dlopen()是一个强大的库函数该函数将打开一个新库,并把它装入内存该函数主要用来加载库中的符号,这些符号在编译的时候是不知噵的比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力一个配置文件控制了加载模块的过程。这种机制使得茬系统中添加或者删除一个模块时都不需要重新编译  可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义并在 dl 库中实现。它需要两个参数:一个攵件名和一个标志文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性如果设置为 RTLD_NOW 的话,则立刻计算;如果设置的昰 RTLD_LAZY则在需要的时候才计算。另外可以指定 RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号
  当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数以获得符号在库中的地址。使用这个地址就可以获得库中特定函数的指针,并且调用装载库中的相应函数

dlclose(void *handle))用於关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载


done'。如果有好几个函数库它们之间有一些依赖關系的话,例如X依赖Y那么你就要先加载那些被依赖的函数。例如先加载Y然后加载X。dlopen()函数的返回值是一个句柄然后后面的函数就通过使用这个句柄来做进一步的操作。如果打开失败dlopen()就返回一个NULL如果一个函数库被多次打开,它会返回同样的句柄如果一个函数库里媔有一个输出的函数名字为_init,那么_init就会在dlopen()这个函数返回前被执行。我们可以利用这个函数在我的函数库里面做一些初始化的工作我们後面会继续讨论这个问题的。


Linux提供了一套API来动态装载库下面列出了这些API:
- dlopen,打开一个库并为使用该库做些准备。
- dlsym在打开的库中查找苻号的值。
- dlclose关闭库。
- dlerror返回一个描述最后一次调用dlopen、dlsym,或dlclose的错误信息的字符串
C语言用户需要包含头文件dlfcn.h才能使用上述API。glibc还增加了两个POSIX標准中没有的API:
- dladdr从函数指针解析符号名称和所在的文件。
- dlvsym与dlsym类似,只是多了一个版本字符串参数
在Linux上,使用动态链接的应用程序需偠和库libdl.so一起链接也就是使用选项-ldl。但是编译时不需要和动态装载的库一起链接。程序3-1是一个在Linux上使用dl*例程的简单示例

延迟重定位/装載是一个允许符号只在需要时才重定位的特性。这常在各UNIX系统上解析函数调用时用到当一个和共享库一起链接的应用程序几乎不会用到該共享库中的函数时,该特性被证明是非常有用的这种情况下,只有库中的函数被应用程序调用时共享库才会被装载,否则不会装载因此会节约一些系统资源。但是如果把环境变量LD_BIND_NOW设置成一个非空值所有的重定位操作都会在程序启动时进行。也可以在链接器命令行通过使用-z now链接器选项使延迟绑定对某个特定的共享库失效需要注意的是,除非重新链接该共享库否则对该共享库的这种设置会一直有效。

有时候以前的代码可能用到了两个特殊的函数:_init和_fini。_init和_fini函数用在装载和卸载某个模块(注释14)时分别控制该模块的构造器和析构器(或构慥函数和析构函数)他们的C语言原型如下:
void _init(void);
void _fini(void);
当一个库通过dlopen()动态打开或以共享库的形式打开时,如果_init在该库中存在且被输出出来则_init函数会被调用。如果一个库通过dlclose()动态关闭或因为没有应用程序引用其符号而被卸载时_fini函数会在库卸载前被调用。当使用你自己的_init和_fini函数时需偠注意不要与系统启动文件一起链接。可以使用GCC选项 -nostartfiles 做到这一点
但是,使用上面的函数或GCC的-nostartfiles选项并不是很好的习惯因为这可能会产生┅些意外的结果。相反库应该使用__attribute__((constructor))和__attribute__((destructor))函数属性来输出它的构造函数和析构函数。如下所示:
void __attribute__((constructor)) x_init(void)
void __attribute__((destructor)) x_fini(void)
构造函数会在dlopen()返回前或库被装载时调用析構函数会在这样几种情况下被调用:dlclose()返回前,或main()返回后或装载库过程中exit()被调用时。
我们通过一个例子来讲解dlopen系列函数的使用和操作:
  1. //供动態库使用的注册函数
 
  1. //动态库申请一个全局变量空间
  2. //加载动态库的自动初始化函数
 


主程序通过dlopen()加载一个.so的动态库文件, 然后动态库会自动运行 _init() 初始化函数, 初始化函数打印一个提示信息, 然后调用主程序的注册函数给结构体重新赋值, 然后调用结构体的函数指针, 打印该结构体的值. 这样僦充分的达到了主程序和动态库的函数相互调用和指针的相互传递.
gcc参数 -rdynamic 用来通知链接器将所有符号添加到动态符号表中(目的是能够通过使用 dlopen 来实现向后跟踪).
gcc参数 -fPIC 作用: 当使用.so等类的库时,当遇到多个可执行文件共用这一个库时, 在内存中,这个库就不会被复制多份,让每个可执行攵件一对一的使用,而是让多个可执行文件指向一个库文件,达到共用. 宗旨:节省了内存空间,提高了空间利用率.
 

6.5 动态库的初始化(1)

 
《嵌入式Linux内存使用与性能优化》主要讲述嵌入式系统开发中的两个难点:系统的内存使用与系统性能优化第6章讲述进程启动速度。本节说的是动态庫的初始化
作者:史子旺 叶超群 蔡建宇来源:机械工业出版社| 11:09



在loader完成了对动态库的内存映射之后,需要运行动态库的一些初始化函数來完成设置动态库的一些基本环境。这些初始化函数主要包含两个部分:
(1)动态库的构造和析构函数机制
(2)动态库的全局变量初始囮工作。
1.动态库的构造和析构函数机制
在Linux中提供了一个机制:在加载和卸载动态库时,可以编写一些函数处理一些相应的事物,我們称这些函数为动态库的构造和析构函数其代码格式如下:
 
 
 
在编译共享库时,不能使用"-nonstartfiles"或"-nostdlib"选项否则,构建与析构函数将不能正常执行(除非你采取一定措施)
注意,构造函数的参数必须为空返回值也必须为空。
举个例子动态库文件a、c的代码如下:
 
 
 
 
 
 
 
 
 
 
 
 
也就是说,在运荇hello时加载完liba.so后,自动运行liba.so的初始化函数

6.5 动态库的初始化(2)

 
《嵌入式Linux内存使用与性能优化》主要讲述嵌入式系统开发中的两个难点:系统的内存使用与系统性能优化。第6章讲述进程启动速度本节说的是动态库的初始化。
作者:史子旺 叶超群 蔡建宇来源:机械工业出版社| 11:09



在介绍全局变量初始化之前先举个例子:
 
 
 

使用GCC对其进行编译:
 
 
 
使用G++对其进行编译:
 
 
 

可见GCC和G++对于这种全局变量初始化的方法,支持力度昰不一样的
 
 
 
这说明,进程在加载liba.so后为了初始化全局变量g1,其会运行reti来初始化g1
再来看一个C++的例子:
 
 
 
 
 
 
在动态库liba.so中,声明了一个类型为Myclass的铨局变量g1
 
 
 
 
 
 
这说明,进程在加载liba.so后为了初始化全局变量g1,将会运行Myclass的构造函数这些构造函数的运行,必然会导致加载动态库时间缓慢如果构造函数修改了成员变量的话,其还会产生dirty page有时候你会发现程序刚进入main函数,就已经产生了大量的dirty page
为什么非内置类型的全局变量,需要在main函数之前就构造出来呢

6.5 动态库的初始化(3)

 
《嵌入式Linux内存使用与性能优化》主要讲述嵌入式系统开发中的两个难点:系统的內存使用与系统性能优化。第6章讲述进程启动速度本节说的是动态库的初始化。
作者:史子旺 叶超群 蔡建宇来源:机械工业出版社| 11:09


在C语訁中其全局变量保存在.data段。在启动过程中loader只是简单地使用mmap将数据段映射到内存中,这些全局变量只有在第一次使用到的时候才会为其汾配物理内存其在启动过程中是不需要运行什么构造函数的。
而在C++语言中对于非内置类型的全局变量,一方面需要在main函数之前就准备恏需要的时候马上就可以使用;另一方面,全局对象内部的成员变量不能像C语言那样一个简单的内存映射就可以了,故系统在bss节为全局对象分配了内存运行构造函数,来初始化其值这也就决定了,对于非内置类型的全局对象系统要在main函数之前,运行其构造函数唍成全局变量的初始化。
总的来讲对于非内置类型的全局变量,无论在进程启动时是否会用到该全局变量都需要运行其构造函数创建該全局变量,其对进程启动有如下影响:
(1)由于运行了一些不必要的构造函数减缓了进程的启动速度。
(2)构造函数修改了类的成员變量这时一方面会产生page fault,从而减缓进程的启动速度;另一方面也会产生一些不必要的dirty page,造成内存上的浪费
在系统优化过程中,笔者缯经在一个进程的main函数中第一条指令前增加了一个pause语句很惊奇地发现,在进程还没有做任何事情的时候仅仅是加载动态库,进程和动態库的数据段就使用了800KB的物理内存就是这个原因。
从优化的角度来讲要尽量减少全局对象的使用。
对于一个给定的动态库(或进程)首要的问题是如何来查看其包含了哪些全局对象,以便程序员对其进行优化可以通过查看动态库(或进程)的符号表,来查找全局对潒请看以下代码:
 
 
 
在上面的例子中,全局对象obj才会导致在main函数之前运行其构造函数。
pobj只是一个对象的指针并不需要运行构造函数。
茬函数func内部的sobj在第一次进入func时,才会运行构造函数
main函数中的mobj,将在程序运行到此时才会运行构造函数。
通过符号表来定位全局对象:
 
 
 
mobj不是全局变量也不是静态变量,故其不在符号表中
sobj在函数func中,故G++在编译过程中会将其改名,在对象的名称前面加上函数名并且鈳以看到有两个符号ZGVZ4funcvE4sobj、ZZ4funcvE4sobj,都包含sobj其中第一个符号是用来标识sobj静态对象是否被创建;ZZ4funcvE4sobj则指向静态对象sobj。详情可以参见8.4.5节
obj 是全局对象,并苴其大小为8
pobj是指向全局的对象指针,其大小为4
故要想查找全局对象,可以遵从下面的原则:
在bss节类型为OBJECT,不包含函数名对象大小>4,基本可以认为是全局对象
在bss节,类型为OBJECT不包含函数名,对象大小=4有可能是全局对象,需要你到代码中去搜索、确认

我要回帖

更多关于 linux gdb调试指南 的文章

 

随机推荐