一:背景

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. QA|如何获取元素属性值|网页计算器自动化测试实战

    一般来说 类似于<value>123</value>这样的元素,我们获取元素值是用.text获取,但有时这个值不是写在这里,而是作为标签的属性值写进去的,此时我们就需要获取属性 ...

  2. 【项目源码】基于JavaEE的健康管理系统

    随着网络技术的不断发展,网站的开发与运用变得更加广泛.这次采用java语言SSH框架(Spring,Struts,Hibernate)设计并实现了面向特定群体的健康管理平台.该网站主要有教师饮食管理. ...

  3. ubuntu22.04 安装nginx 卸载

    1.使用apt-get 安装nginx(得机器能联网才行) # 切换到root用户 # 切换到root用户 sudo -i # 更新apt源 apt-get update# 安装nginx apt-g ...

  4. 其它——Postman做接口测试

    文章目录 一 介绍 二 下载安装 三 使用 四 批量接口测试(创建collections) 五 导出与导入同事的接口 5.1 导出 5.2 导入 一 介绍 在前后端分离开发时,后端工作人员完成系统接口 ...

  5. Vue2系列(lqz)——slot插槽 (内容分发)、2 transition过渡、3 生命周期、4 swiper学习、5 自定义组件的封装、6 自定义指令、7 过滤器

    文章目录 1 slot插槽 (内容分发) 1.1 基本使用 1.2 插槽应用场景1 1.3 插槽应用场景2 1.4 具名插槽 2 transition过渡 3 生命周期 4 swiper学习 5 自定 ...

  6. ElasticSearch系列——倒排索引、删除映射类型、打分机制、配置文件、常见错误

    文章目录 1 倒排索引 2 删除映射类型 一 前言 二 什么是映射类型? 三 为什么要删除映射类型? 四 映射类型的替代方法 4.1 将映射类型分开存储在索引中 4.2 自定义类型字段回到顶部 五 没 ...

  7. Python正则表达式——常用re正则表达式集合

    文章目录 一.校验数字的表达式 二.校验字符的表达式 三.特殊需求表达式 一.校验数字的表达式 数字:^[0-9]*$ n位的数字:^\d{n}$ 至少n位的数字:^\d{n,}$ m-n位的数字:^ ...

  8. Python面试——基础面试题

    文章目录 1.Python 和 Java.PHP.C.C#.C++等其他语言的对比? C语言既有高级语言的特点,又具有汇编语言的特点,它是结构式语言.C语言应用指针:可以直接进行靠近硬件的操作,但是C ...

  9. Langchain-Chatchat项目:1-整体介绍

      基于Langchain与ChatGLM等语言模型的本地知识库问答应用实现.项目中默认LLM模型改为THUDM/chatglm2-6b[2],默认Embedding模型改为moka-ai/m3e-b ...

  10. 轻量通讯协议 --- MQTT

    介绍 一.MQTT简介 MQTT(Message Queuing Telemetry Transport) 是一种轻量级的消息传输协议,通常用于在物联网(IoT)和传感器网络中进行通信.它设计用于在低 ...