linux probe函数调用,Linux系统调试之return probe原理和示例

那么return probe如何实现呢?

我们知道,hook一个函数的起始位置非常容易,拿函数名当指针,直接修改成0xcc或者别的什么call/jmp即可,而hook一个函数的结束就没有这么简单了:

函数大小不容易计算。

函数可以在任意位置调用return。

怎么办呢?

很简单,只要执行流到了函数里面,直接取RSP寄存器指示的地址即可,它就是函数返回的地址,hook这个地址,就OK了。

于是,方法也就有了:

在函数开头打int3断点(也可以ftrace,但这里仅谈int3)。

在函数调用时的int3处理函数中获取stack上的return address。

将return adress替换成int3的address(也可以用单独的函数)。

在return address的int3处理函数中调用return probe函数。

恢复正常流程。

如下图所示:

e4aca07ff27d3747ce6a1b2a21f3d051.png

下面是一个示例程序:

#include

#include

#include

// sigframe的RIP偏移

#define PC_OFFSET192

// sigframe的RSP偏移

#define SP_OFFSET184

// sigframe的RAX偏移,用于获取返回值

#define AX_OFFSET168

#defineI_BRK0xcc

unsigned long *orig;

void trap(int unused);

void fbrk()

{

asm ("nop;");

}

unsigned char *pbrk;

void breakpoint(unsigned long addr)

{

unsigned char *page;

signal(SIGTRAP, trap);

page = (unsigned char *)((unsigned long)addr & 0xffffffffffff1000);

mprotect((void *)page, 4096, PROT_WRITE|PROT_READ|PROT_EXEC);

page = (unsigned char *)((unsigned long)fbrk & 0xffffffffffff1000);

mprotect((void *)page, 4096, PROT_WRITE|PROT_READ|PROT_EXEC);

// 配置int3 buffer,用于替换return address。

pbrk = page;

*pbrk = I_BRK;

// 保存函数头的原始指令,用于restore。

orig = (unsigned long *)*(unsigned long *)addr;

// 函数开头打断点。

*(unsigned char *)(addr) = I_BRK;

}

void trap(int unused)

{

unsigned long *p;

static int ret = 0;

if (ret == 0) { // 函数开头的int3处理

p = (unsigned long *)((unsigned char *)&p + PC_OFFSET);

// 恢复原始指令。

*p = *p - 1;

*(unsigned long *)*p = (unsigned long)orig;

// 保存原始的返回地址。

orig = (unsigned long *)*(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET);

// 替换返回地址为int3 buffer。

*(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET) = (unsigned long)pbrk;

ret = 1;

} else if (ret == 1) { // 函数返回时的int3处理

p = (unsigned long*)((unsigned char *)&p + PC_OFFSET);

printf("浙江温州皮鞋湿,下雨进水不会胖。[%d]\n", *(int *)((unsigned char *)&p + AX_OFFSET));

// 更改函数的返回值,仅做测试...

*(int *)((unsigned char *)&p + AX_OFFSET) = 1222;

// 恢复原始流程。

*p = (unsigned long)orig;

ret = 0;

}

}

// 测试函数,返回值为参数。

int test_function(int ret)

{

printf("[test function]\n");

return ret;

}

int main(int argc, char **argv)

{

int ret = 0;

ret = atoi(argv[1]);

breakpoint((unsigned long)&test_function);

printf("before call: %d\n", ret);

ret = test_function(ret);

printf("after call: %d\n", ret);

}

OK,编译,运行,看效果:

[root@localhost probe]# gcc retdebug.c -O0 -o retdebug

[root@localhost probe]# ./retdebug 12345

before call: 12345

[test function]

浙江温州皮鞋湿,下雨进水不会胖。[12345]

after call: 1222

成功打印了一句话并修改了返回值。

其实,内核中的kretprobe差不多也就是这个意思。

哦,不,你看我把return handler实现在trap信号处理函数里了,这并不好。不过在我的例子里,仅仅是打印一句话,所以也就无所谓了,真正正确的做法是,单独写一个stub,来call return handler,而不是用int3来中转:

; 汇编实现的stub

asm_stub:

SAVE_ALL;

call ret_handler;

RESTORE_ALL;

push _orig_;

ret;

// 更加简洁的trap函数

void trap(int unused)

{

unsigned long *p;

p = (unsigned long *)((unsigned char *)&p + PC_OFFSET);

// 恢复原始指令。

*p = *p - 1;

*(unsigned long *)*p = (unsigned long)orig;

// 保存原始的返回地址。

_orig_ = (unsigned long *)*(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET);

// 替换返回地址为int3 buffer。

*(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET) = (unsigned long)asm_stub;

}

嗯,这才是正确的方法:

28cfbabf3f43f60e46199350fc2c4fdb.png

后记

虽然我一直都在顽强得抗争着,但我感觉我的精神已经达到了顶点,很难再次突破,所以,我决定开始学习编程,顺便考个中级职称!基础差,底子薄并不可怕,过不了几个月,我应该就不会再说自己不会编程了,也算一件幸事!

浙江温州皮鞋湿,下雨进水不会胖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用Linux SPI总线和W5500芯片的驱动程序示例,可供参考: ```c #include <linux/kernel.h> #include <linux/module.h> #include <linux/spi/spi.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #define W5500_SPI_MODE (SPI_MODE_0) #define W5500_SPI_BITS_PER_WORD (8) #define W5500_SPI_MAX_SPEED_HZ (10 * 1000 * 1000) struct w5500_data { struct spi_device *spi; struct gpio_desc *reset_gpio; }; static int w5500_reset(struct w5500_data *w5500) { if (!w5500->reset_gpio) return -ENODEV; gpiod_set_value_cansleep(w5500->reset_gpio, 1); msleep(10); gpiod_set_value_cansleep(w5500->reset_gpio, 0); msleep(10); gpiod_set_value_cansleep(w5500->reset_gpio, 1); msleep(150); return 0; } static int w5500_probe(struct spi_device *spi) { struct device *dev = &spi->dev; struct w5500_data *w5500; int ret; w5500 = devm_kzalloc(dev, sizeof(*w5500), GFP_KERNEL); if (!w5500) return -ENOMEM; spi->bits_per_word = W5500_SPI_BITS_PER_WORD; spi_setup(spi); w5500->spi = spi; w5500->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(w5500->reset_gpio)) { ret = PTR_ERR(w5500->reset_gpio); dev_err(dev, "Failed to get reset GPIO: %d\n", ret); return ret; } ret = w5500_reset(w5500); if (ret) { dev_err(dev, "Failed to reset W5500: %d\n", ret); return ret; } return 0; } static int w5500_remove(struct spi_device *spi) { struct w5500_data *w5500 = spi_get_drvdata(spi); if (w5500_reset(w5500)) { dev_warn(&spi->dev, "Failed to reset W5500 on removal\n"); } return 0; } static const struct of_device_id w5500_of_match[] = { { .compatible = "wiznet,w5500", }, { }, }; MODULE_DEVICE_TABLE(of, w5500_of_match); static struct spi_driver w5500_driver = { .driver = { .name = "w5500", .of_match_table = w5500_of_match, }, .probe = w5500_probe, .remove = w5500_remove, }; module_spi_driver(w5500_driver); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("W5500 SPI driver"); MODULE_LICENSE("GPL"); ``` 此驱动程序在设备树中使用以下属性: ```dts spi { #address-cells = <1>; #size-cells = <0>; w5500@0 { compatible = "wiznet,w5500"; reg = <0>; spi-max-frequency = <10000000>; reset-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; }; }; ``` 该驱动程序使用 `devm_gpiod_get()` 函数获取重置 GPIO 引脚,以及 `gpiod_set_value_cansleep()` 函数将 GPIO 引脚设置为适当的值。它还使用 SPI API 函数来设置 SPI 总线并进行数据传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值