在不需要的系统调用上引发异常
Raise Exception on unwanted syscall
我被告知要修复遗留应用程序中的错误。
我可以重现错误,但我不知道在哪个 python 源代码行执行了错误。
我可以看到 strace
的相关失败:一个文件被打开,不应该被打开。
我想让相关的 open() linux-syscall 在 python 解释器中引发异常。我的目标:我希望查看堆栈跟踪以修复错误。
这样我就可以避免使用调试器单步执行大量行的耗时。
与其他词相同:如果系统调用被执行,导致 open("/somefile", O_RDONLY) = 4
的 strace 输出,python 解释器应该退出并回溯。
有人解决吗?
如果你不明白我在找什么,请发表评论。
我们可以在导入模块之前对 open
做一个补丁,这里是一个例子:
在test.py
中:
def func():
with open('test', 'w') as f:
pass
在 test2.py
中:
try:
import __builtin__ # for python2
except ImportError:
import builtins as __builtin__ #for python3
import copy
import traceback
orig_open = copy.copy(__builtin__.open)
def myopen(*args):
traceback.print_stack()
return orig_open(*args)
__builtin__.open = myopen
from test import func # Note that we import the module after patching on open()
func()
并且在test2.py
中调用func()
时,将打印调用堆栈:
$ python test2.py
File "test2.py", line 19, in <module>
func()
File "/tmp/test.py", line 4, in func
with open('test', 'w') as f:
File "test2.py", line 12, in myopen
traceback.print_stack()
您可以 运行 python 在 gdb 下,在 open()
系统调用(或者更确切地说,调用它的 libc 中的存根函数)上设置一个(条件)断点, 并且, 当遇到断点时, 向 python 进程发送一个 SIGINT
信号并让它继续, 因此 python 脚本的执行应该被中断并使用所需的堆栈跟踪.
下面的 shell 脚本可自动执行该过程。
用法:
stack_trace_on_open
filename
-- python
script.py
[
script args
]
stack_trace_on_open:
#!/usr/bin/env bash
myname="$(basename "[=10=]")"
if [[ $# -lt 4 || "" != '--' ]]
then
echo >&2 "Usage: $myname <filename> -- python <script.py> [script args ...]"
exit 1
fi
fname=
python_exe=""
shift 3
gdb -q "$python_exe" <<END
set breakpoint pending on
break open
condition 1 strcmp($rdi,"$fname") == 0
run "$@"
signal 2
cont
quit
END
示范:
$ cat test.py
import ctypes
clib = ctypes.CDLL(None)
fd = clib.open("/dev/urandom", 0)
clib.close(fd)
$ ./stack_trace_on_open /dev/urandom -- python test.py
Reading symbols from python...(no debugging symbols found)...done.
(gdb) (gdb) Function "open" not defined.
Breakpoint 1 (open) pending.
(gdb) (gdb) Starting program: /usr/bin/python "test.py"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, open64 () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) Continuing with signal SIGINT.
Breakpoint 1, open64 () at ../sysdeps/unix/syscall-template.S:84
84 in ../sysdeps/unix/syscall-template.S
(gdb) Continuing.
Traceback (most recent call last): # <--------
File "test.py", line 4, in <module> # <--------
fd = clib.open("/dev/urandom", 0) # <--------
KeyboardInterrupt
[Inferior 1 (process 14248) exited with code 01]
(gdb)
您可以使用 strace
命令行工具的 -e inject
。
man strace
-e inject=syscall_set
Perform syscall tampering for the specified set of syscalls.
The syntax of the syscall_set specification
is the same as in the -e trace option.
At least one of error, retval, signal, delay_enter,
or delay_exit options has to be specified. error and
retval are mutually exclusive.
If :error=errno option is specified, a fault is injected
into a syscall invocation: the syscall number is
replaced by -1 which corresponds to an invalid syscall
(unless a syscall is specified with :syscall= option),
and the error code is specified using a symbolic errno
value like ENOSYS or a numeric value within
1..4095 range.
我被告知要修复遗留应用程序中的错误。
我可以重现错误,但我不知道在哪个 python 源代码行执行了错误。
我可以看到 strace
的相关失败:一个文件被打开,不应该被打开。
我想让相关的 open() linux-syscall 在 python 解释器中引发异常。我的目标:我希望查看堆栈跟踪以修复错误。
这样我就可以避免使用调试器单步执行大量行的耗时。
与其他词相同:如果系统调用被执行,导致 open("/somefile", O_RDONLY) = 4
的 strace 输出,python 解释器应该退出并回溯。
有人解决吗?
如果你不明白我在找什么,请发表评论。
我们可以在导入模块之前对 open
做一个补丁,这里是一个例子:
在test.py
中:
def func():
with open('test', 'w') as f:
pass
在 test2.py
中:
try:
import __builtin__ # for python2
except ImportError:
import builtins as __builtin__ #for python3
import copy
import traceback
orig_open = copy.copy(__builtin__.open)
def myopen(*args):
traceback.print_stack()
return orig_open(*args)
__builtin__.open = myopen
from test import func # Note that we import the module after patching on open()
func()
并且在test2.py
中调用func()
时,将打印调用堆栈:
$ python test2.py
File "test2.py", line 19, in <module>
func()
File "/tmp/test.py", line 4, in func
with open('test', 'w') as f:
File "test2.py", line 12, in myopen
traceback.print_stack()
您可以 运行 python 在 gdb 下,在 open()
系统调用(或者更确切地说,调用它的 libc 中的存根函数)上设置一个(条件)断点, 并且, 当遇到断点时, 向 python 进程发送一个 SIGINT
信号并让它继续, 因此 python 脚本的执行应该被中断并使用所需的堆栈跟踪.
下面的 shell 脚本可自动执行该过程。
用法:
stack_trace_on_open
filename
-- python
script.py
[
script args
]
stack_trace_on_open:
#!/usr/bin/env bash
myname="$(basename "[=10=]")"
if [[ $# -lt 4 || "" != '--' ]]
then
echo >&2 "Usage: $myname <filename> -- python <script.py> [script args ...]"
exit 1
fi
fname=
python_exe=""
shift 3
gdb -q "$python_exe" <<END
set breakpoint pending on
break open
condition 1 strcmp($rdi,"$fname") == 0
run "$@"
signal 2
cont
quit
END
示范:
$ cat test.py
import ctypes
clib = ctypes.CDLL(None)
fd = clib.open("/dev/urandom", 0)
clib.close(fd)
$ ./stack_trace_on_open /dev/urandom -- python test.py
Reading symbols from python...(no debugging symbols found)...done.
(gdb) (gdb) Function "open" not defined.
Breakpoint 1 (open) pending.
(gdb) (gdb) Starting program: /usr/bin/python "test.py"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, open64 () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) Continuing with signal SIGINT.
Breakpoint 1, open64 () at ../sysdeps/unix/syscall-template.S:84
84 in ../sysdeps/unix/syscall-template.S
(gdb) Continuing.
Traceback (most recent call last): # <--------
File "test.py", line 4, in <module> # <--------
fd = clib.open("/dev/urandom", 0) # <--------
KeyboardInterrupt
[Inferior 1 (process 14248) exited with code 01]
(gdb)
您可以使用 strace
命令行工具的 -e inject
。
man strace
-e inject=syscall_set
Perform syscall tampering for the specified set of syscalls.
The syntax of the syscall_set specification
is the same as in the -e trace option.
At least one of error, retval, signal, delay_enter,
or delay_exit options has to be specified. error and
retval are mutually exclusive.
If :error=errno option is specified, a fault is injected
into a syscall invocation: the syscall number is
replaced by -1 which corresponds to an invalid syscall
(unless a syscall is specified with :syscall= option),
and the error code is specified using a symbolic errno
value like ENOSYS or a numeric value within
1..4095 range.