专栏首页一个程序员的修炼之路GDB读取动态库中定义的全局变量错误

GDB读取动态库中定义的全局变量错误

最近看了一篇getopt使用的文章,为了追踪其执行的逻辑,于是采用GDB挂载调试的方式进行查看。但却出现了GDB打印全局变量optind的时候出现错误。

问题发现和描述

首先optind是使用getopt时候的全局变量,表示使用getopt时候的下一个argv的指针索引。在应用程序调试的时候设置了多个参数,但是随着多次调用getopt,全局变量optind通过gdb打印出来的值却总是1, 但是通过程序打印出来的optind确实是逐步变化的。奇怪了,gdb怎么会有这么明显的bug呢?

于是我在程序中打印出optind的地址为0x600D60.

+++++++++++++++++++++

The optind address is 0x600d60.

+++++++++++++++++++++

那么我们看看gdb打印出来的optind的地址又是多少呢? 如下所示,OMG,他们的地址居然不一样!

+++++++++++++++++++++

(gdb) p &optind $1 = (int *) 0x3bcfd5210c

+++++++++++++++++++++

问题的探讨

根据多方面的探索,后来查看到,这个涉及到一个“Copy Relocation”的技术。也就是动态库中存在全局变量的时候,在编译阶段已经在程序的.BSS段中预留了控件给动态库中的全局变量,然后当程序初始化的时候,会拷贝动态库中的全局变量到程序预留的.BSS段控件;其他所有的动态库,也将访问通过前面所说的.BSS段中的全局变量来访问原先动态库中定义的全局变量。 具体关于Copy Relocation的机制可以参考文章:http://www.shrubbery.net/solaris9ab/SUNWdev/LLM/p22.html#CHAPTER4-84604

回到原先的问题,那么GDB打印出来并不是程序中.BSS通过Copy Relocation产生的全局变量optind, 而是打印的libc.so中原有的变量的值。

那么怎么打印出正确的值呢?

首先我们通过"Info var optind"查看下optind相关的信息,可以看到两处指名了optind的出处,第一处其实说明了这个是在libc.so中定义的,而gdb默认打印的也是libc.so中定义的,第二处就是之前所说的通过"Copy Relocation"技术存储的optind的实际使用的地址,其地址也是"0x600D60"。

++++++++++++++++++++

(gdb) info var optind All variables matching regular expression "optind": File /usr/include/getopt.h: static int optind; Non-debugging symbols: 0x0000000000600d60 optind@@GLIBC_2.2.5

++++++++++++++++++++

现在我们就可以通过命令[p 'optind@@GLIBC_2.2.5']打印出实际使用的optind的值。

其实一个简单的问题背后,会隐藏着很多技术和机制。而要真正的明白问题产生的根本原因,目前所掌握的知识还远远不够,楼主一定还需再接再厉。以后还会针对这个问题引申出来的GOT,Copy Relocation等好好的研究研究。

参考

1. gdb does not print right value!

http://www.linuxquestions.org/questions/programming-9/gdb-does-not-print-right-value-908538/

2. optind

https://sourceware.org/ml/gdb/2003-12/msg00160.html

3. Copy Relocation

http://www.shrubbery.net/solaris9ab/SUNWdev/LLM/p22.html#CHAPTER4-84604

4. What's the purpose of copy relocation? http://stackoverflow.com/questions/26863009/whats-the-purpose-of-copy-relocation

本文分享自微信公众号 - 一个程序员的修炼之路(CoderStudyShare),作者:iceking

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2015-09-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • C++又一坑:动态链接库中的全局变量

    前几天我们项目的日志系统出现了一点问题,但是一直没有时间去深究。 昨天在同事的帮助下,无意中猜了一种可能性,结果还真被我猜中了,于是今天就特别研究了一下,记录...

    owent
  • 很经典的GDB调试命令,包括查看变量,查看内存

    在你调试程序时,当程序被停住时,你可以使用print命令(简写命令为p),或是同义命令inspect来查看当前程序的运行数据。print命令的格式是: prin...

    用户1215536
  • Linux调试工具

    Printf(“valriable x has value = %d\n”, x)

    战神伽罗
  • 面试汇总(七):操作系统常见面试总结(三):操作系统其它问题

    前两篇文章我们分别介绍了在面试中操作系统有关线程和进程和系统中的一些问题常见的面试题。这篇文章我们继续给大家介绍常见的问题。这篇文章将给大家介绍操作系统中系统相...

    一计之长
  • linux后台开发常用调试工具

    一、编译阶段 nm 获取二进制文件包含的符号信息 strings 获取二进制文件包含的字符串常量 strip...

    李海彬
  • 如何优雅的调试段错误

    摘要:当程序运行出现段错误时,目标文件没有调试符号,也没配置产生 core dump,如何定位到出错的文件和函数,并尽可能提供更详细的一些信息,如参数,代码等...

    F-Stack
  • 在 Linux 上创建并调试转储文件

    崩溃转储、内存转储、核心转储、系统转储……这些全都会产生同样的产物:一个包含了当应用崩溃时,在那个特定时刻应用的内存状态的文件。

    用户8639654
  • linux下的程序调试方法汇总

    搞电子都知道,电路不是焊接出来的,是调试出来的。程序员也一定认同,程序不是写出来的,是调试出来的。那么调试工具就显得尤为重要,linux作为笔者重要的开发平台,...

    战神伽罗
  • 故障分析 | 全局读锁一直没有释放,发生了什么?

    爱可生交付服务部团队北京 DBA,主要负责处理 MySQL 的 troubleshooting 和我司自研数据库自动化管理平台 DMP 的日常运维问题,对数据库...

    爱可生开源社区
  • 修改,编译,GDB调试openjdk8源码(docker环境下)

    在上一章《在docker上编译openjdk8》里,我们在docker容器内成功编译了openjdk8的源码,有没有读者朋友产生过这个念头:“能不能修改open...

    程序员欣宸
  • 从内存布局上看,Rust的胖指针到底胖在栈上还是堆上?

    最近我在前辈巨师的带领下,也进入到学习Rust的大军中,与其它语言一样,Rust最初的爬坡难点也在于字符串方面的处理。虽然说Rust与C一样也有指针概念,但是在...

    MikeLoveRust
  • GDB实现原理和使用范例

    这篇文章为了让你深入了解gdb的工作原理,以及如何在linux环境下使用强大的gdb调试程序功能。

    mariolu
  • gdb基础命令和常用操作补充

    GDB是Unix下的一个程序调试工具,类似于windows下面的VC调试器,区别在于GDB采用全命令行控制。 使用GDB需要在编译时使用-g选项,gcc支持-...

    s1mba
  • GDB调试core文件样例(如何定位Segment fault)

    core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump....

    阳光岛主
  • 40.Linux应用调试-使用gdb和gdbserver

    1.gdb和gdbserver调试原理 通过linux虚拟机里的gdb,来向开发板里的gdbserver发送命令,比如设置断点,运行setp等,然后开发板上的g...

    张诺谦
  • linux内核启动过程分析

    start_kernel是内核启动阶段的入口,通过单步调试,可以发现它是linux内核执行的第一个init,我们单步进入看看它做了哪些操作:

    De4dCr0w
  • 程序异常分析指南

    在Linux上编写运行C语言程序,经常会遇到程序崩溃、卡死等异常的情况。程序崩溃时最常见的就是程序运行终止,报告Segmentation fault (core...

    Florian
  • 学pwn 经典堆栈的缓冲区溢出

    $gcc -g -Wall hello.c -o hello $g++ -g -Wall hello.cpp -o hello

    用户5878089
  • FFLIB C++ 异步&类型安全&printf风格的日志库

    摘要       C++程序的调试一般有调试器、printf、日志文件三种。Linux下的调试器为gdb,关于gdb的使用甚至可以单独用一本书来说明,但是本章并...

    知然

扫码关注云+社区

领取腾讯云代金券