当前位置: 首页 > article >正文

链接加载与ATT汇编

链接加载与AT&T汇编

水一水又一篇
在这里插入图片描述

用kali或者Ubuntu

文章目录

  • 链接加载与AT&T汇编
    • 1. 链接加载相关
      • 1. gcc,readelf,objdump,ld,ldd
      • 2. gdb
      • 3. strace
      • 4. ELF相关
      • 5. 静态链接小测试
        • 正常能跑的程序
        • 不能正常跑的程序
      • 6. 动态链接
        • 基本款
        • 豪华款(默认选项,使用过程链接表(PLT表))
    • 2. 内联汇编(AT&T汇编)
    • 参考

1. 链接加载相关

编译器gcc,汇编器as,链接器ld,调试器gdb,追踪器strace/ltrace,profiler(perf)

1. gcc,readelf,objdump,ld,ldd

gcc
-O2 编译优化级别设置为O2
-fno-pic 关闭位置无关代码(PIC)的生成
-g 生成调试信息,可用gdb进行调试
-c 生成.o文件(只编译不链接)
-l 指定头文件搜索路径
-L 指定库文件搜索路径
-m32 生成32位程序
-static 静态链接
-v 这个选项会显示编译器的版本信息以及编译和链接过程中的详细信息
-z execstack 启用可执行堆栈
-fno-stack-protector 禁用栈保护
-no-pie 关闭PIE保护
添加Toolchain Test Builds PPA(推荐,以获取最新版GCC):
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update

安装新版本的GCC(例如安装GCC 7):
sudo apt install gcc-7 g++-7

添加gcc和g++版本条目:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 60 --slave /usr/bin/g++ g++ /usr/bin/g++-5
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 --slave /usr/bin/g++ g++ /usr/bin/g++-7

如果有多个GCC版本,设定命令的默认版本:
sudo update-alternatives --config gcc
readelf -a main
用于显示ELF文件的所有信息,包括二进制和调试信息。
-h:文件头
-S:段表
-s:符号表
-d: 查看依赖库
-p:查看某个段内容,非常重要。如:readelf -p .comment libc.so (通过-p对只读段的查看就可以替代strings命令)


objdump -d main.o
反汇编程序
-d:反汇编所有代码段
-D:在-d基础上还包括符号表和调试信息
-h:显示文件节头信息
-r:显示重定位表信息
-t:显示BSS段内容
–s:代码段、数据段、只读数据段,各个段二进制
-a:看一个.a静态库文件中包含了哪些目标文件
ld a.o b.o c.o
-e:程序从某个函数开始。
-o:指定输出文件的名称。
-l:链接指定的库文件。
-L:添加库搜索路径。
-r:生成可重定位的输出。
-noinhibit-exec:忽略非致命错误。
-v:显示链接信息。
ldd ./a.out
显示一个可执行文件或者共享库(动态链接库)所依赖的共享库,本质是个脚本vim $(which ldd)

2. gdb

gcc编译时须带-g,不然没有行号信息。

命令含义
start开始执行
r重新执行
n单步步过
ni单步步过(汇编级别)
s单步步入(C语言级别)
si单步步入(汇编级别)
c继续运行
q退出
b设置断点
普通断点:b file.c:行号或者b mainb 行号
条件断点:b file.c:行号 if num == 2
查看断点:info b
删除断点:del 2
禁用启用:disable / enable 2
watch观察断点
监控指针*pwatch *p
监控数组a[10]watch a
监控局部变量num:watch num
catch捕捉断点
clear删除断点
bt查看函数调用堆栈信息
display查看变量值display num
程序每暂停一次,自动打印变量num的值
启用/禁用:disable / enable display num
list默认显示当前行的上5行和下5行的源代码

打印

p var 打印变量的值
p &var 打印变量地址
p *addr 打印所指向的值
p /x var 十六进制显示值

用gdb查看内存

一般格式: x /nfu

x/50x $rsp打印堆栈前50个内存单元的内容,每个单元默认占用4个字节

说明

x 是 examine 的缩写

n表示要显示的内存单元的个数

f表示显示方式, 可取如下值
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
f 按浮点数格式显示变量。
c 按字符格式显示变量。
i 指令地址格式
a 按十六进制格式显示变量。


u表示一个地址单元的长度
b表示单字节,
h表示双字节,
w表示四字节,
g表示八字节

其他格式指令

`x/s 地址` 用于显示内存中的字符串
x/10s $esp-144   
#$esp-144处的内存以字符串形式显示

`x/i 地址` 命令用于显示内存中的指令

图形化调试界面

layout src 显示源代码窗口
layout asm 显示汇编代码窗口
layout regs 显示寄存器窗口

3. strace

跟踪系统调用和接收的信号

strace -o output.txt -T -tt -e trace=all -p 28979
#跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间-T,以及开始时间-tt(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。

strace -f -F -o ./output.txt dcopserver
#这里 -f -F选项告诉strace同时跟踪fork和vfork出来的进程,-o选项把所有strace输出写到./output.txt里面,dcopserver是要启动和调试的程序。

4. ELF相关

ELF文件类型说明实例
可重定位文件
(Relocatable File)
这类文件包括代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可归为这一类Linux的.o
Windows的.obj
可执行文件
(Executable File)
这类文件包含了可以直接执行的程序,它的代表就是ELF可执行文件/bin/bash,a.out
Windows的.exe
共享目标文件
(Shared Object File)
这种文件包含了代码和数据,可以在下面两种情况下使用:
1.链接器使用此文件和其他可重定位文件和共享目标文件链接,产生新的目标文件;
2.动态链接器将几种共享目标文件与可执行文件结合,作为进程映像的一部分运行
Linu的.so(E. g.:/lib/glibc-2.5.so
windows的DLL
核心转储文件
(Core Dump File)
当进程意外终止时,系统可以将该进程地址空闲的内容及终止时的一些信息转储到此linux下的core dump

在这里插入图片描述

.text 代码节
.rodata 只读数据节
.data 已初始化的全局变量或静态变量
.bss 指未初始化(或初始化为0)的全局变量或静态变量
.symtab 符号表
.rel 重定位信息
section header table 节头表

5. 静态链接小测试

vim a.c

int foo(int a,int b){
	return a+b;
}

vim b.c

int x=100,y=200;

vim main.c

extern int x,y;
int foo(int a,int b);
int main(){
    printf("%d+%d=%d\n",x,y,foo(x,y));
    return 0;
}
正常能跑的程序
gcc -O2 -fno-pic -c ./a.c
gcc -O2 -fno-pic -c ./b.c
gcc -O2 -fno-pic -c ./main.c
gcc -static a.o b.o main.o
./a.out
strace ./a.out
readelf -h ./a.out

在这里插入图片描述

在这里插入图片描述

objdump -D ./main.o

在这里插入图片描述

可以看到链接前call(e8)后的地址为空

readelf -a main.o

在这里插入图片描述

可以看到左侧0x11也表示我们要填函数地址的地方(e8后四字节)。

为什么call xxxxxx填写的foo的地址要减4?

因为call xxx指令长度不固定,所以xxx的偏移是相对于call xxx的下一条指令的偏移

在这里插入图片描述

objdump -D ./a.out

在这里插入图片描述

readelf -a ./a.out

在这里插入图片描述

可以看到符号表的0x60019c表示y,并且注意到foo的地址为0x400126,刚好等于0x0021+0x400105

不能正常跑的程序
gcc -O2 -fno-pic -c ./a.c
gcc -O2 -fno-pic -c ./b.c
gcc -O2 -fno-pic -c ./main.c
ld a.o b.o main.o -noinhibit-exec
./a.out

在这里插入图片描述

段错误

gdb ./a.out
start

在这里插入图片描述

可以看到printf这里变成了call 0x0,访问了非法地址。

ld只链接了a.o,b.o,c.o,gcc还链接了标准库相关的东西

在这里插入图片描述

6. 动态链接

https://ysyx.oscc.cc/slides/hello-x86.html

ELF查表

基本款

call *table[printf]

在链接时,填入运行时的table

使用 -fno-plt选项开启(不要在位置无关的代码中使用 PLT 进行外部函数调用。相反,在调用站点从 GOT(全局偏移表) 加载被调用者地址并跳转到该地址。)

gcc版本不要太低

gcc -c -fno-plt ./a.c
gcc -c -fno-plt ./b.c
gcc -c -fno-plt ./main.c
gcc -fno-plt a.o b.o main.o
豪华款(默认选项,使用过程链接表(PLT表))

Procedure Linkage Table

调用printf函数的过程

gcc默认将printf优化成puts但下文仍然采用对printf的介绍

  1. 通过gcc编译链接的程序默认采用延迟绑定技术,对printf的调用会跳转到PLT表项printf@plt

  2. 该表项为一个桩函数,将跳转到对应的GOT表项所指的位置

  3. 由于进程初次调用printf()函数,该GOT表项默认指向PLT[0],PLT[0]跳转到位于GOT[2]的动态链接器延迟绑定函数_dl_runtime_resolve()

  4. _dl_runtime_resolve()将获取printf()函数的实际地址,将该地址写入到printf对应的GOT表项中(后续对printf()的调用可通过上述桩函数直接进入printf()函数执行),跳转到printf()执行,printf()函数最终将调用系统级I/O函数write(1,"hello world\n",13)

printf@plt:
	jmp *table[PRINTF]
	push $PRINTF
	call resolve
gcc -c ./a.c
gcc -c ./b.c
gcc -c ./main.c
gcc a.o b.o main.o

/lib64/ld-linux-x86-64.so.2 ./a.out./a.out真正再操作系统里的行为,这与Sha-Bang(#!)的实现机制相同

在这里插入图片描述

在这里插入图片描述

初次调用

在这里插入图片描述
再次调用

在这里插入图片描述

2. 内联汇编(AT&T汇编)

注意内联汇编与x86汇编区别

内联汇编目的操作数在右边,x86汇编目的操作数在左边

add 0x4(%esp),%eax ;add eax,[esp+0x4]
lea (%rdi,%rsi,1),%eax ;lea eax,[rdi+rsi*1]

立即数 $13$0x80

寄存器 %rax,实际写代码要多加一个%

标签 %l[label1]

换行 \n\t

%0一般表示第一个用到的寄存器

寻址方式

100
//访问当前段:100处内存
%es:100
//访问es:100处内存
(%eax)
//访问当前段:eax指向内存,类似于[eax]
(%eax,%ebx)
//访问当前段:(eax+ebx)处内存
(%ecx,%ebx,2)
//访问当前段:(ecx+ebx*2)处内存
(,%ebx,2)
//访问当前段:(ebx*2)处内存
-10(%eax)
//访问当前段:(eax-10)处内存,类似于[eax-10]
%ds:-10(%ebp)
//访问ds:(ebp-10)处内存
add -0x4(%esp),%eax
//add eax,[esp-0x4]
jmp *%rax //用寄存器%rax中的值作为跳转目标
jmp *(%rax) //以%rax中的值作为读地址,在从内存中读出跳转目标

助记符后缀

b:1个字节
w:2个字节
l:4个字节
q:8个字节
比如:movl $100,%es:(%eax)

cpp书写格式

asm(assembler template
	:output operands(optional)
	:input operands(optional) 
	:list of clobbers registers(optional) //需要使用的寄存器
);

asm goto(
	assembler template
	:output operands(optional)
	:input operands(optional) 
	:list of clobbers registers(optional) //需要使用的寄存器
	:gotolabels //实现汇编的跳转
);
asm volatile() //不做编译优化

[[asmSymbolicName]] constraint (cvariablename)
constraint:
m 内存
r 寄存器
i 立即数 
constraint modifier characters 对于output是必须的
= 只写
+ 读写
#include <stdio.h>
int inc1(int src) {
	int dst;
	asm volatile("mov %1,%0\n\t" //%0表示dst(eax),%1表示src
		"add $1,%0"
		:"=r"(dst)
		: "r"(src));
	return dst;
}
int inc2(int src) {
	int dst;
	asm volatile("mov %1,%0\n\t" //%0表示dst(eax),%1表示src
		"mov $3,%%eax\n\t"
		"add $1,%0"
		:"=r"(dst)
		: "r"(src));
	return dst;
}
int inc3(int src){
	int dst;
	asm volatile("mov %1,%0\n\t"  
                 //%0表示dst(edx),%1表示src
		"mov $3,%%eax\n\t"
		"add $1,%0"
		:"=r"(dst)
		:"r"(src)
		:"%eax");
	return dst;
}
int main(int argc, char* argv[]) {
	printf("inc1:%d\n", inc1(1));
	printf("inc2:%d\n", inc2(1));
	printf("inc3:%d\n", inc3(1));
	return 0;
}

编译链接一下

gcc ./test1.c
./a.out

在这里插入图片描述

反汇编

objdump -d ./a.out

在这里插入图片描述

可以看到第三个函数相较于第二个函数使用了edx做加法,最后再给回eax作为返回值,避免了eax复用导致的问题。

参考

链接与加载 why_hy_y


http://www.kler.cn/a/505134.html

相关文章:

  • Unity中实现倒计时结束后干一些事情
  • 【Go】Go Gin框架初识(一)
  • stack_queue的底层,模拟实现,deque和priority_queue详解
  • Node.js - HTTP
  • C#类型转换
  • WeakAuras NES Script(lua)
  • 【DevOps】Pipeline功能语法
  • 从0到1搭建推荐系统 -- 数据驱动的算法与架构设计(带数据集)
  • 脚本练习3
  • 统计学习算法——逻辑斯谛回归
  • vue3计算属性
  • G1原理—5.G1垃圾回收过程之Mixed GC
  • 报告分享 | 大语言模型安全和隐私研究综述
  • 使用 WPF 和 C# 绘制覆盖网格的 3D 表面
  • CF 368A.Sereja and Coat Rack(Java实现)
  • uniapp 小程序 textarea 层级穿透,聚焦光标位置错误怎么办?
  • next-auth v5 结合 Prisma 实现登录与会话管理
  • NVIDIA PyTorch Docker 镜像安装
  • RustDesk ID更新脚本
  • macos 一直报错 XXX 将对你的电脑造成伤害。你应该将它移到废纸篓
  • VSCode开发STM32,并支持C++
  • Spring官网构建Springboot工程
  • 【llama_factory】qwen2_vl训练与批量推理
  • DAMA GDPA 备考笔记(二)
  • 3.flask蓝图使用
  • 【优选算法篇】--双指针篇