在 LLDB 中,
step
命令作为单个“语句”跨过整个 asm{}
块。 有没有办法让它单独处理每条指令,这样你就不必使用 si
来单步执行指令,并切换到反汇编模式来查看当前位置?
作为我正在参加的课程的一部分,我们正在用 C 语言编写内联汇编。他们在 Windows 上使用 Visual Studio 来编写和调试 MSVS 程序集。我想在我的 M2 Macbook 上安装并运行它(我可以运行虚拟机,但这很无聊)
编译这个东西非常容易。我使用
clang
并像这样复制:
clang -fasm-blocks -target x86_64-apple-macos main.c -g -O0
这是一个示例代码片段:
#include <inttypes.h>
#include <stdio.h>
uint8_t count1(uint32_t x) {
uint8_t out;
__asm {
mov edx, x
mov al, 0
next: cmp edx, 0
je done
mov cl, dl
and cl, 1
add al, cl
shr edx, 1
jmp next
done:
mov out, al
}
return out;
}
int main() {
uint32_t x = 0x5789ABCD;
uint8_t cnt = count1(x);
printf("Number of 1s in 0x%X: %hhu\n", x, cnt);
}
我唯一需要做的不同的事情是返回值。我必须手动将其移至 C 变量并返回。如果您知道如何隐式地完成这项工作,那就太棒了,但这对我来说不是什么大问题。
问题来自于调试。我使用 neovim,并且对
nvim-dap
和 codelldb
有很好的调试经验。然而,当我单步执行上面截取的代码时,asm 块一步运行。
我尝试在 cli 上使用 raw lldb
进行调试,它做了同样的事情。我可以使用 si
和 di -F intel
单步执行 asm,但这有点麻烦。
我想我只是在编译步骤中缺少一个标志来为 asm 块生成调试符号。有谁知道如何逐步通过
__asm
中的 LLDB
块以及 Neovim ?我对问题的理解正确吗?
所以,我找到了解决方案。事实证明,您可以手动指定位置。这就是它的样子:
uint8_t count1(uint32_t x) {
int out;
__asm {
.file 1 "main.c"
.loc 1 9
mov edx, [x]
.loc 1 11
mov al, 0
.loc 1 13
next: cmp edx, 0
.loc 1 15
je done
.loc 1 17
mov cl, dl
.loc 1 19
and cl, 1
.loc 1 21
add al, cl
.loc 1 23
shr edx, 1
.loc 1 25
jmp next
done:
.loc 1 28
mov out, al
}
return out;
}
手动执行此操作会非常烦人,并且修改源代码会弄乱源代码在调试器中的外观(您会看到
.loc ...
声明)
我们可以做的是使用 clang 将 c 文件编译为中间 LLVM IR,使用自定义编译器通道或简单脚本(我选择了脚本)对其进行修改,然后将 IR 编译为可执行文件:
clang -S -emit-llvm -o main.ll main.c -fasm-blocks -target x86_64-apple-macos -g -O0
python3 preprocess.py main.ll
clang main.ll -o a.out -fasm-blocks -target x86_64-apple-macos -g -O0
这将创建一个包含所有调试符号和原始源代码的可执行文件,这就是我最初想要的。
这里是 Python 脚本,供感兴趣的人参考:
import sys
current_file_no = 0
def get_file_no():
global current_file_no
current_file_no += 1
return current_file_no
def process_asm(asm_string: str, source_filename: str, first_line: int) -> str:
instructions = asm_string.split("\\0A\\09")
file_no = get_file_no()
result = [f".file {file_no} \\22{source_filename}\\22"]
loc_directives = [
f".loc 1 {line}" for line in range(first_line, first_line + len(instructions))
]
for loc, inst in zip(loc_directives, instructions):
result.append(loc)
result.append(inst)
return "\\0A\\09".join(result)
asm_prefix = "call void asm sideeffect inteldialect "
def main():
filename = sys.argv[1]
with open(filename, "r") as f:
lines = f.readlines()
source_filename = None
result = []
for line in lines:
stripped_line = line.strip()
if stripped_line.startswith("source_filename"):
source_filename = stripped_line.split('"')[1]
result.append(line)
continue
if stripped_line.startswith("call void asm sideeffect inteldialect "):
start = line.find(asm_prefix) + len(asm_prefix) + 1
end = start
while line[end] != '"':
end += 1
asm_string = line[start:end]
dbg_entry = line.split("!dbg ")[1].split(",")[0]
di_location = [ln for ln in lines if ln.startswith(dbg_entry)][0]
line_number = int(di_location.split("line: ")[1].split(",")[0])
assert source_filename is not None
new_asm = process_asm(asm_string, source_filename, line_number + 1)
result.append(line[:start] + new_asm + line[end:])
continue
result.append(line)
with open(filename, "w") as f:
f.write("".join(result))
if __name__ == "__main__":
main()