hook linux bash,linux 下利用gdb + bash 实现类似gflags的功能

#!/bin/bash

while [ -z "$GDB_READY" ] ; do

echo sleep 1 second...

export FORCE_REFRESH_ENV=1

sleep 1

export -n FORCE_REFRESH_ENV

done

exec ./test2.real "$@"

END

这段脚本偷梁换柱取代了原来的ELF格式可执行文件, 在它被执行时, 不停地循环, 循环的条件是检测一个叫

GDB_READY的环境变量.

这样, 在该程序被调用时, 相当于阻塞在了exec调用上, 原因是该环境变量不存在.

在另一终端中, 用ps ax | grep test2 找到PID,

gdb -p PID

调试该bash 进程. 通过

p setenv("GDB_READY", "1")

为该进程设置环境变量, 从而退出上面的while循环, 在设置之后, 先不能急于continue, 因为断点还没设置.

symbol-file ./test2.real -readnow

可以在尚未load该进程时, 提前告之gdb,

然后可以:

b test2.c:main

set follow-fork-mode child

continue

会在该进程的main处断住.

bash脚本中FORCE_REFRESH_ENV 是最让人迷惑的地方, 如果没有export + export -n, 整个方案就不行, 这是因为bash会对环境变量作一个cache, 这两个动作保证能引发它刷新该cache, 这样下一次循环时才能判断出变量GDB_READY 是否真正设置了.

bash对 $GDB_READY的实现并不是直接调用 getenv, 而是在一个内部的hash中去找.

======================================================

看来bash对环境变量的处理不象我想象的那么简单.

上述步骤有时有效, 有时无效, 无效的原因是环境变量仍未得到更新.

曾经尝试在sleep 调用处额外调用一个

bash -c 'echo ENV=$GDB_READY'

因为这样的调用bash若要保证正确性一定要刷新一遍env变量, 试验后可以, 但不能保证永远是正确的.

100601125053.png

以上是bash脚本不能正确获取环境变量时的情境. 总之, 何时同步进程的环境变量到bash的内部hash, 现在看来不象想象中那么简单可以通过一些操作准确地触发

最后, 我找到了一个不需要手工去设置symbol-file的办法, 只需要在__libc_start_main上设置一个断点, 这个符号是libc的导出符号, 所以即使没有libc的debug info也总是可以在其上设置断点的, 在此处断住, 主程序的main还没到, 而symbol 已经由gdb自动装入, 可以设置断点了.

这个办法更简洁有效. 但仍需要使用前面的环境变量.

其实一个利用文件的略加变通的办法即可避开环境变量:

#!/bin/bash

echo 0 > ~/gdb_ready.txt

while [ "$(cat ~/gdb_ready.txt)" != "1" ] ; do

sleep 1

done

exec ./test2.real "$@"

END

脚本总是将该文件内容设置为0, 这样进入死循环, 直到在gdb 中, 通过命令

shell echo 1 > ~/gdb_ready.txt

continue

才可继续执行到最终程序, 仍可配置上面的 __libc_start_main 办法.

另一个可行的方案:

#!/bin/bash

LD_PRELOAD=/home/zhao/my_preload.so exec ./test2.real "$@"

这个办法中, 需要一个额外的my_preload.so 文件, 但bash脚本和设置断点等都变得简单, 原因是程序执行到my_preload.so中的_init函数内的死循环时, gdb已然把目标程序的调试符号载入, 此时可以从容地设置断点, 不需再用__libc_start_main.

至于my_preload.so 文件, 最好用绝对路径, 其好处是: gdb -p 来调试程序时总能找到该文件.

其内容如下:

#include /* sleep */

int _init(void)

{

volatile gdb_ready = 0;

while( !gdb_ready )

{

sleep(1);

}

return 0;

}

gcc -nostartfiles -g -fPIC -shared -o my_preload.so my_preload.c

-nostartfiles to avoid multiple definition of _init error

you can even write a simple function gdb_go in ~/gdbinit to make things easiler:

define gdb_go

set gdb_ready=1

continue

end

gdb函数中设置的变量gdb_ready必需与my_preload.c 中的变量名保持一致. 这个方案看似麻烦一些, 因为要有一个额外的.c 文件, 但实际上, 对于一个已经安装好的系统, 只需做一次即可. 对所有被调试程序都是一样的.

为了能给被调试程序"安装"上述的bash脚本来做这件事, 下面的bash函数可以自动安装和反安装这样的脚本:

#arg 1: the absolute path to an executable file

function debug_hook_check_permission()

{

abs_path="$1"

if [ ! -x "$abs_path" ]; then

echo "Fail to find executable file $1, please check the existence of the file and wether it can be found in \$PATH" >&2

return 1

fi

# Check access right

if ! ls "$abs_path" >& /dev/null; then

echo "You have no permission to access [$abs_path]" >&2

return 1

fi

# Check write access

dir_name="${abs_path%/*}"

if [ ! -w "${dir_name}" ]; then

echo "You have no write permission to directory [${dir_name}]" >&2

return 1

fi

return 0

}

# arg 1: the program name, absolute path or relative path or can found in $PATH

# If have insufficient permission, return 1 with error message write to stderr

function install_debug_hook()

{

abs_path=$(which "$1")

# General check

if ! debug_hook_check_permission "$abs_path" ; then

return 1

fi

# check: program should be ELF executable

if [[ "$(eval file \"$abs_path\")" =~ "ELF.*executable" ]] ; then

true

else

echo "file [$abs_path] is not an ELF executable" >&2

file "$abs_path"

return 1

fi

# check: program.real should not exist

if [ -e "$abs_path.real" ] ; then

echo "File [$abs_path.real] already exist!" >&2

ls -l "$abs_path.real"

return 1

fi

# Rename to file to a suffix ".real"

mv "$abs_path" "$abs_path.real"

echo "[$abs_path]" renamed to "[$abs_path.real]"

# Create the file

echo "file [$abs_path]"

echo '#!/bin/bash' | tee "$abs_path"

echo 'LD_PRELOAD=~/my_preload.so exec '"$abs_path.real"' "$@"' | tee -a "$abs_path"

chmod a+x "$abs_path"

}

function uninstall_debug_hook()

{

abs_path=$(which "$1")

# check: program must be a shell script which contains 2 lines

if [[ "$(eval file \"$abs_path\")" =~ "shell script" ]] && [ "$(eval cat \"$abs_path\" | wc -l)" -eq 2 ]; then

true

else

echo "File [$abs_path] is not recognized as shell script or not a 2 lines file" >&2

file "$abs_path"

echo

cat "$abs_path"

return 1

fi

rm "$abs_path"

mv "$abs_path.real" "$abs_path"

echo "[$abs_path.real]" renamed to "[$abs_path]"

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值