好吧,这是一个棘手的问题。
我有一个 CMake 项目来构建可执行文件,然后我有一个单独的独立“目标”,它允许我运行一个 Python 脚本,该脚本使用子进程来调用
gdb
并检查此可执行文件。请注意,我在 Windows 10 上的 MINGW64 (MSYS2) bash
shell 中运行所有这些。
生成示例文件的脚本作为
gen_files.sh
发布在帖子末尾;要生成示例,请运行以下 bash
命令:
mkdir C:/tmp/cmake_py_gdb
cd C:/tmp/cmake_py_gdb
# save the file as gen_files.sh there
bash gen_files.sh
mkdir build
cd build
cmake ../ -DCMAKE_BUILD_TYPE=Debug -G "MSYS Makefiles"
make
./myprogram.exe
# to trigger the freeze:
make py_gdb_inspect
这里是棘手的部分:
make py_gdb_inspect
本质上调用了python3 py_gdb_inspect.py
;但是,如果我直接从 MINGW64 python3 py_gdb_inspect.py
shell 调用 bash
,一切都会按预期工作:
$ (cd C:/tmp/cmake_py_gdb; python3 py_gdb_inspect.py)
> Working directory C:\tmp\cmake_py_gdb.
> Working directory C:\tmp\cmake_py_gdb\build.
> Before start_engine...<function start_engine at 0x000001bb42f94ae0>
> start_engine
> Starting debug engine/process: "myprogram.exe"
> resp1:
> resp2: Breakpoint 1 at 0x1400014d3: file C:/tmp/cmake_py_gdb/myprogram.c, line 11.
> [New Thread 20724.0x3c1c]
> mynumber_val: -1
>
> [... snip empty lines ...]
>
py_gdb_inspect.py finished!
但是,当我调用
make py_gdb_inspect
时,脚本冻结在这里:
$ make py_gdb_inspect
make[1]: Entering directory '/c/tmp/cmake_py_gdb/build'
make[2]: Entering directory '/c/tmp/cmake_py_gdb/build'
make[3]: Entering directory '/c/tmp/cmake_py_gdb/build'
make[3]: Leaving directory '/c/tmp/cmake_py_gdb/build'
make[3]: Entering directory '/c/tmp/cmake_py_gdb/build'
[100%] run py_gdb_inspect.py in C:/tmp/cmake_py_gdb
> Working directory C:\tmp\cmake_py_gdb.
> Working directory C:\tmp\cmake_py_gdb\build.
> Before start_engine...<function start_engine at 0x0000011da8014ae0>
> start_engine
> Starting debug engine/process: "myprogram.exe"
> resp1:
> resp2: Breakpoint 1 at 0x1400014d3: file C:/tmp/cmake_py_gdb/myprogram.c, line 11.
...我什至无法用 CTRL+C 来阻止它,我必须使用
pkill -9 -f python
,make
也会输出:
make[3]: *** [CMakeFiles/py_gdb_inspect.dir/build.make:58: CMakeFiles/py_gdb_inspect] Killed
make[3]: Leaving directory '/c/tmp/cmake_py_gdb/build'
make[2]: *** [CMakeFiles/Makefile2:110: CMakeFiles/py_gdb_inspect.dir/all] Error 2
make[2]: Leaving directory '/c/tmp/cmake_py_gdb/build'
make[1]: *** [CMakeFiles/Makefile2:117: CMakeFiles/py_gdb_inspect.dir/rule] Error 2
make[1]: Leaving directory '/c/tmp/cmake_py_gdb/build'
make: *** [Makefile:131: py_gdb_inspect] Error 2
我的猜测是,可能当
gdb
启动“[新线程20724.0x3c1c]”时,终端的stdout/stderr文件描述符会变得混乱——特别是因为这里我们有一个调用链: -> python (MINGW64 的那个) -> gdb -> python (GDB 内置的那个) [1]
不幸的是,我无法弄清楚究竟发生了什么 - 所以我的问题是:如何强制
python3 py_gdb_inspect.py
命令完成,而不是冻结,以及通过 make py_gdb_inspect
调用它时?
gen_files.sh
read -r -d '' README <<'EOF'
To run this example:
mkdir C:/tmp/cmake_py_gdb
cd C:/tmp/cmake_py_gdb
# save this file as gen_files.sh there
bash gen_files.sh
mkdir build
cd build
cmake ../ -DCMAKE_BUILD_TYPE=Debug -G "MSYS Makefiles"
make
./myprogram.exe
# to trigger the freeze:
make py_gdb_inspect
EOF
cat > myprogram.c <<EOF
#include <stdio.h>
int add_two_vars(int a, int b) {
int result = a + b;
return result;
}
int mynumber = -1;
int main() {
int a = 5;
int b = 2;
mynumber = add_two_vars(a, b);
printf("Hello from myprogram.exe! mynumber is %d\n", mynumber);
return 0;
}
EOF
cat > CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.13)
find_package(Python3 COMPONENTS Interpreter)
message(STATUS
"Python: version=${Python3_VERSION} interpreter=${Python3_EXECUTABLE}")
if(NOT Python3_FOUND)
# find_package() will not abort the build if anything's missing.
string(JOIN "\n" errmsg
" Python3 not found."
" - Python3_FOUND=${Python3_FOUND}"
)
message(FATAL_ERROR ${errmsg})
endif()
project(myprogram C)
set(CMAKE_C_STANDARD 11)
set(${PROJECT_NAME}_sources
myprogram.c
)
add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_sources})
add_custom_target(py_gdb_inspect
${Python3_EXECUTABLE} py_gdb_inspect.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "run py_gdb_inspect.py in ${CMAKE_CURRENT_SOURCE_DIR}"
)
EOF
cat > py_gdb_inspect.py <<'EOF'
import sys,os
import subprocess
GDB = "D:/msys64/mingw64/bin/gdb-multiarch.exe"
# GDB scripts are known as "GDB command files";
# extension ".gdb" for "GDB's own command language"
GDB_TEST_SCRIPT = "test_cmdfile.gdb"
GDB_TEST_CMD = [ GDB, "-q", "-batch", "-x", GDB_TEST_SCRIPT ]
try:
result = subprocess.Popen(GDB_TEST_CMD, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, cwd=os.getcwd(), bufsize=1, close_fds=True )
except Exception as ex:
print(ex)
sys.exit(1)
while result.poll() is None: # for line in iter(result.stdout.readline, b''):
tline = result.stdout.readline().strip()
print("> {}".format(tline))
print("py_gdb_inspect.py finished!")
EOF
cat > test_cmdfile.gdb <<'EOF'
python
import time
def start_engine(in_exe, printresp=False):
print('start_engine')
print('Starting debug engine/process: "{}"'.format(in_exe))
response_1 = gdb.execute('file {}'.format(in_exe), to_string=True)
if printresp: print("resp1: " + response_1.strip())
# silent breakpoint (avoid printing "Thread 1 hit Breakpoint 1"): https://stackoverflow.com/q/58528800
response_2 = gdb.execute("b main\ncommands\nsilent\nend", to_string=True)
if printresp: print("resp2: " + response_2.strip())
# FOR SOME REASON, now when we run gdb.execute('r') when script is called from make, it BLOCKS! from_tty=True does not help
#time.sleep(1)
#response_3 = gdb.execute('r', to_string=True, from_tty=True)
#if printresp: print("resp3: " + response_3.strip())
def print_value():
mynumber_val = gdb.execute('printf "%d", mynumber', to_string=True)
print("mynumber_val: {}".format(mynumber_val))
end
pwd
cd build
pwd
python print("Before start_engine...{}".format(start_engine))
python start_engine("myprogram.exe", printresp=True)
#tty gdb_tty.txt
run
py print_value()
EOF
[1] 抱歉,我忍不住指出了这样一个梗:“哟,老兄,我喜欢 Python,所以我在你的 gdb 中放了一个 Python,这样你就可以在使用 Python 时使用 Python。”
:)
找到它 - 解决方案是将
creationflags
与 subprocess.CREATE_NEW_CONSOLE
或 subprocess.DETACHED_PROCESS
一起使用 - 不幸的是,在这两种设置中,都会短暂显示 cmd.exe
窗口,并且似乎不可能隐藏它,这令人恼火 - 然而,更烦人的冻结问题解决了!
result = subprocess.Popen(GDB_TEST_CMD, \
stdout=subprocess.PIPE, \
stderr=subprocess.STDOUT, \
universal_newlines=True, \
cwd=os.getcwd(), \
bufsize=1, \
close_fds=True, \
creationflags = ( subprocess.CREATE_NEW_CONSOLE | subprocess.SW_HIDE | subprocess.CREATE_NO_WINDOW ) # SW_HIDE and CREATE_NO_WINDOW still show window; either DETACHED_PROCESS or CREATE_NEW_CONSOLE works (CREATE_BREAKAWAY_FROM_JOB, CREATE_NEW_PROCESS_GROUP does not work, still freezes then) \
)