一:背景

1. 讲故事

在高级调试的旅行中,发现有不少人对符号表不是很清楚,其实简而言之符号表中记录着一些程序的生物特征,比如哪个地址是函数(签名信息),哪个地址是全局变量,静态变量,行号是多少,数据类型是什么 等等,目的就是辅助我们可视化的调试,如果没有这些辅助我们看到的都是一些无意义的汇编代码,逆向起来会非常困难,这一篇我们就来系统的聊一聊。

二:程序编译的四个阶段

1. 案例代码

要想理解符号表,首先需要理解 代码文件 是如何变成 可执行文件 的,即如下的四个阶段。

  • 预处理阶段
  • 编译阶段
  • 汇编阶段
  • 链接阶段

为了能够看到每一个阶段,用 gcc 的相关命令手工推进,并用 chatgpt 写一段测试代码,包含全局变量,静态变量,函数等信息。


#include <stdio.h>
#define PI 3.1415926 int global_var = 10; void func() { static int static_var = 5; printf("global_var = %d, static_var = %d PI=%f\n", global_var, static_var,PI); global_var++;
static_var++;
} int main() {
func();
func(); return 0;
}

接下来用 gcc --help 命令查看下需要使用的命令列表。


[root@localhost data]# gcc --help
Usage: gcc [options] file...
Options:
-E Preprocess only; do not compile, assemble or link
-S Compile only; do not assemble or link
-c Compile and assemble, but do not link
-o <file> Place the output into <file>
...

2. 预编译阶段

预处理主要做的就是代码整合,比如将 #include 文件导入,将 #define 宏替换等等,接下来使用 gcc -E 进行预处理。


[root@localhost data]# gcc main.c -E -o main.i
[root@localhost data]# ls
main.c main.i

可以看到这个 main.c 文件已经膨胀到了 858 行了。

3. 编译阶段

前面阶段是把代码预处理好,接下来就是将C代码编译成汇编代码了,使用 gcc -S 即可。


[root@localhost data]# gcc main.c -S -o main.s -masm=intel
[root@localhost data]# ls
main.c main.i main.s

从图中可以看到汇编代码中也有很多辅助信息,比如 global_var 是一个 @object 变量,类型为 int,在 .rodata 只读数据段中,目的就是给汇编阶段打辅助。

4. 汇编阶段

有了汇编代码之后,接下来就是将 汇编代码 转成 机器代码,这个阶段会产生二进制文件,并且会构建 section 信息以及符号表信息,可以使用 gcc -c 即可。


[root@localhost data]# gcc main.c -c -o main.o -masm=intel
[root@localhost data]# ls
main.c main.i main.o main.s

二进制文件模式默认是不能可视化打开的,可以借助于 objdump 工具。


[root@localhost data]# objdump
-h, --[section-]headers Display the contents of the section headers
-t, --syms Display the contents of the symbol table(s) [root@localhost data]# objdump -t main.o main.o: file format elf64-x86-64 SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000004 l O .data 0000000000000004 static_var.2179
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .data 0000000000000004 global_var
0000000000000000 g F .text 0000000000000058 func
0000000000000000 *UND* 0000000000000000 printf
0000000000000058 g F .text 000000000000001f main

在上面的符号表中看到了 func函数以及 static_varglobal_var 以及所属的 section。

5. 链接阶段

这个阶段主要是将多个二进制代码文件进一步整合变成可在操作系统上运行的可执行文件,可以使用 gcc -o


[root@localhost data]# gcc main.c -o main
[root@localhost data]# ls
main main.c main.i main.o main.s
[root@localhost data]# ./main
global_var = 10, static_var = 5 PI=3.141593
global_var = 11, static_var = 6 PI=3.141593
[root@localhost data]# objdump -t main main: file format elf64-x86-64 SYMBOL TABLE:
...
0000000000601034 g O .data 0000000000000004 global_var
0000000000601034 g O .data 0000000000000004 global_var
...
000000000040052d g F .text 0000000000000058 func
...

相比汇编阶段,这个阶段的 符号表 中的第一列都是有地址值的,是相对模块的偏移值,比如说: module+0x000000000040052d 标记的是 func 函数。

上面是 linux 上的可执行文件的符号表信息,有些朋友说我是 windows 平台上的,怎么看符号表信息呢?

三:Windows 上的 pdb 解析

1. 观察 pdb 文件

上一节我们看到的是 linux 上 elf格式 的可执行文件,这一节看下 windows 平台上的PE文件 的符号表信息是什么样的呢?有了前面四阶段编译的理论基础,再聊就比较简单了。

在 windows 平台上 符号表信息 是藏在 pdb 文件中的,这种拆开的方式是有很大好处的,如果需要调试代码,windbg 会自动加载 pdb 文件,无调试的情况下就不需要加载 pdb 了,减少了可执行文件的大小,也提升了性能。

接下来用 SymView.exe 这种工具去打开 pdb 文件,截图如下:

从图中可以看到,符号表信息高达 10968 个,并且 func 函数的入口地址是在 module +0x11870 处,相当于做了一个标记,接下来我们拿这个func做一个测试。

2. 有 pdb 的 func 函数

首先说一下为什么通过 exe 可以找到 pdb,这是因为 PE 头的 DIRECTORY_ENTRY_DEBUG 节中记录了 pdb 的地址。

只要这个路径有 pdb 就可以在 windbg 运行中按需加载了,然后通过 u MySample.exe+0x11870 观察,截图如下:

图中显示的非常清楚,地址 00fd1870 就是 func 的入口地址,让一个无意义的地址马上有意义起来了,哈哈~~~

3. 无 pdb 的 func 函数

这一小节是提供给好奇的朋友的,如果没有 pdb,那汇编上又是一个什么模样,为了找到 func 的入口地址,我们内嵌一个 int 3 ,然后把 pdb 给删掉,代码如下:


int main() { __asm {
int 3;
}
func();
func(); return 0;
}

从图中可以看到,func 标记已经没有了,取而代之的都是 module+0xxx,这就会给我们逆向调试带来巨大的障碍。

三: 总结

总而言之,符号表就是对茫茫内存进行标记,就像百度地图一样,让我们知道某个经纬度上有什么建筑,让无情的地理坐标更加有温度,让世界更美好。

聊一聊 .NET高级调试 中必知的符号表的更多相关文章

  1. 浏览器调试的必知必会,零基础足够详细-第一节console面板、移动端调试

    前言 本文已经发布视频点击查看 开发过程中,浏览器的调试非常重要,可以说是必备的技巧,本文我就会分享一些自己掌握的技巧,欢迎补充 我们默认使用Chrome浏览器,但是你使用新edge浏览器也是可以的 ...

  2. 高级程序员必知必会,一文详解MySQL主从同步原理,推荐收藏

    1. MySQL主从同步实现方式 MySQL主从同步是基于Bin Log实现的,而Bin Log记录的是原始SQL语句. Bin Log共有三种日志格式,可以binlog_format配置参数指定. ...

  3. Mach-O在内存中符号表地址、字符串表地址的计算

    KSCrash 是一个用于 iOS 平台的崩溃捕捉框架,最近读了其部分源码,在 KSDynamicLinker 文件中有一个函数,代码如下: /** Get the segment base addr ...

  4. .NET程序员项目开发必知必会—Dev环境中的集成测试用例执行时上下文环境检查(实战)

    Microsoft.NET 解决方案,项目开发必知必会. 从这篇文章开始我将分享一系列我认为在实际工作中很有必要的一些.NET项目开发的核心技术点,所以我称为必知必会.尽管这一系列是使用.NET/C# ...

  5. Visual Studio 使用及调试必知必会

    原文:Visual Studio 使用及调试必知必会   一:C# CODING 技巧 1:TODO 然后 CTRL + W + T,打开任务列表,选中 Comments,就会显示所有待做的任务 2: ...

  6. input屏蔽历史记录 ;function($,undefined) 前面的分号是什么用处 JSON 和 JSONP 两兄弟 document.body.scrollTop与document.documentElement.scrollTop兼容 URL中的# 网站性能优化 前端必知的ajax 简单理解同步与异步 那些年,我们被耍过的bug——has

    input屏蔽历史记录   设置input的扩展属性autocomplete 为off即可 ;function($,undefined) 前面的分号是什么用处   ;(function($){$.ex ...

  7. 第4节:Java基础 - 必知必会(中)

    第4节:Java基础 - 必知必会(中) 本小节是Java基础篇章的第二小节,主要讲述抽象类与接口的区别,注解以及反射等知识点. 一.抽象类和接口有什么区别 抽象类和接口的主要区别可以总结如下: 抽象 ...

  8. SQL 必知必会·笔记<11>创建高级联结

    1. 使用表别名 SQL 除了可以对列名和计算字段使用别名,还允许给表名起别名.这样 做有两个主要理由: 缩短SQL 语句: 允许在一条SELECT 语句中多次使用相同的表. 使用表别名示例: SEL ...

  9. mysql必知必会-创建高级联结

    使用表别名 使用别名引用被检索的表列 别名除了用于列名和计算字段外,SQL还允许给表名起别名.这样做 有两个主要理由: 缩短SQL语句: 允许在单条 SELECT 语句中多次使用相同的表. 可以看到, ...

  10. SQL 必知必会

    本文介绍基本的 SQL 语句,包括查询.过滤.排序.分组.联结.视图.插入数据.创建操纵表等.入门系列,不足颇多,望诸君指点. 注意本文某些例子只能在特定的DBMS中实现(有的已标明,有的未标明),不 ...

随机推荐

  1. C语言下载minGW地址

    https://sourceforge.net/projects/mingw-w64/files/ 下载红框内即可

  2. [编程基础] Python内置模块collections使用笔记

    collections是Python标准库中的一个内置模块,它提供了一些额外的数据结构类型,用于增强Python基础类型如列表(list).元组(tuple)和字典(dict)等.以下是对collec ...

  3. Matplotlib(一)

    Matplotlib(一) Matplotlib库的介绍 Matplotlib库的使用 Matplotlib库由各种可视化类构成,内部结构复杂,受Matlab启发matplotlib.pyplot是绘 ...

  4. KRPANO资源分析工具下载720YUN全景图

    提示:目前分析工具中的全景图下载功能将被极速全景图下载大师替代,相比分析工具,极速全景图下载大师支持更多的网站(包括各类KRPano全景网站,和百度街景) 详细可以查看如下的链接: 极速全景图下载大师 ...

  5. KRPANO资源分析工具模板链接下载

    KRPano资源分析工具1.4.0加入了模板链接下载,可以批量下载有规律的链接. 模板链接基本规则 数字递增链接 pic[1-100]:会生成pic1,pic2,-pic100的链接 pic[a-z] ...

  6. Python脚本批量造数据、跑定时任务协助测试

    批量造数据 连接Mysql的信息 1 import pymysql 2 # 数据库连接信息 3 # 多个库要有多个conn 4 conn = pymysql.connect( 5 host=" ...

  7. linux文件、目录权限和所有者

    文件.目录权限和所有者 简介:用户对一个文件或目录具有访问权限,这些访问权限决定了谁能访问,以及如何范围这些文件和目录.通过设置权限可以限制或允许以下三种用户访问: 文件的用户所有者(属主) 文件的组 ...

  8. 给定3个整数a、b、c,计算表达式(a+b)/c的值,/是整除运算。[无解]

    题目4-2:给定3个整数a.b.c,计算表达式(a+b)/c的值,/是整除运算. 给定3个整数a.b.c,计算表达式(a+b)/c的值,/是整除运算. 输入格式:输入仅一行,包括三个 整数a.b.c, ...

  9. Eolink Apikit 如何进行自动化测试?

    自动化测试是一种软件测试方法,利用自动化工具和脚本来执行测试用例,以验证软件应用程序的功能.性能.稳定性等特性.自动化测试的主要目的是提高测试效率.减少测试成本,并确保软件的质量和可靠性. 作为测试人 ...

  10. 从零用VitePress搭建博客教程(4) – 如何自定义首页布局和主题样式修改?

    接上一节:从零用VitePress搭建博客教程(3) - VitePress页脚.标题logo.最后更新时间等相关细节配置 六.首页样式修改 有时候觉得自带的样式不好看,想自定义,首先我们在docs/ ...