decode_stacktrace: make stack dump output useful again

介绍了一款脚本工具,该工具能将Linux内核的堆栈转译为源文件及行号,极大提升了问题定位效率。通过解析堆栈信息,能够帮助开发者快速找到问题发生的具体位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文转载至:http://lwn.net/Articles/592724/

转载说明:就是一个脚本,可以解析内核的 stack dump 信息,将偏移量转换成函数的源文件及行数。


From: Sasha Levin <sasha.levin@oracle.com>
To: torvalds@linux-foundation.org
Subject: [PATCH v3] decode_stacktrace: make stack dump output useful again
Date: Sat, 29 Mar 2014 00:07:08 -0400
Message-ID: <1396066028-29051-1-git-send-email-sasha.levin@oracle.com>
Cc: linux-kernel@vger.kernel.org, michael.ploujnikov@oracle.com, Sasha Levin <sasha.levin@oracle.com>
Archive-link: ArticleThread

Right now when people try to report issues in the kernel they send stack
dumps to eachother, which looks something like this:

[    6.906437]  [<ffffffff811f0e90>] ? backtrace_test_irq_callback+0x20/0x20
[    6.907121]  [<ffffffff84388ce8>] dump_stack+0x52/0x7f
[    6.907640]  [<ffffffff811f0ec8>] backtrace_regression_test+0x38/0x110
[    6.908281]  [<ffffffff813596a0>] ? proc_create_data+0xa0/0xd0
[    6.908870]  [<ffffffff870a8040>] ? proc_modules_init+0x22/0x22
[    6.909480]  [<ffffffff810020c2>] do_one_initcall+0xc2/0x1e0
[...]

However, most of the text you get is pure garbage.

The only useful thing above is the function name. Due to the amount of
different kernel code versions and various configurations being used, the
kernel address and the offset into the function are not really helpful in
determining where the problem actually occured.

Too often the result of someone looking at a stack dump is asking the person
who sent it for a translation for one or more 'addr2line' translations. Which
slows down the entire process of debugging the issue (and really annoying).

The decode_stacktrace script is an attempt to make the output more useful and
easy to work with by translating all kernel addresses in the stack dump into
line numbers. Which means that the stack dump would look like this:

[  635.148361]  dump_stack (lib/dump_stack.c:52)
[  635.149127]  warn_slowpath_common (kernel/panic.c:418)
[  635.150214]  warn_slowpath_null (kernel/panic.c:453)
[  635.151031]  _oalloc_pages_slowpath+0x6a/0x7d0
[  635.152171]  ? zone_watermark_ok (mm/page_alloc.c:1728)
[  635.152988]  ? get_page_from_freelist (mm/page_alloc.c:1939)
[  635.154766]  __alloc_pages_nodemask (mm/page_alloc.c:2766)

It's pretty obvious why this is better than the previous stack dump before.

Usage is pretty simple:

        ./decode_stacktrace.sh [vmlinux] [base path]

Where vmlinux is the vmlinux to extract line numbers from and base path is
the path that points to the root of the build tree, for example:

        ./decode_stacktrace.sh vmlinux /home/sasha/linux/ < input.log > output.log

The stack trace should be piped through it (I, for example, just pipe
the output of the serial console of my KVM test box through it).

Reviewed-by: Michael Ploujnikov <michael.ploujnikov@oracle.com>
Signed-off-by: Sasha Levin <sasha.levin@oracle.com>
---
 scripts/decode_stacktrace.sh |  105 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100755 scripts/decode_stacktrace.sh

diff --git a/scripts/decode_stacktrace.sh b/scripts/decode_stacktrace.sh
new file mode 100755
index 0000000..222fcf4
--- /dev/null
+++ b/scripts/decode_stacktrace.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+# (c) 2014, Sasha Levin <sasha.levin@oracle.com>
+#set -x
+if [[ $# != 2 ]]; then
+	echo "Usage:"
+	echo "	$0 [vmlinux] [base path]"
+	exit 1
+fi
+
+vmlinux=$1
+basepath=$2
+
+parse_symbol() {
+	# The structure of symbol at this point is:
+	#   [name]+[offset]/[total length]
+	#
+	# For example:
+	#   do_basic_setup+0x9c/0xbf
+
+
+	# Strip the symbol name so that we could look it up
+	local name=${symbol%+*}
+
+	# Use 'nm vmlinux' to figure out the base address of said symbol.
+	# It's actually faster to call it every time than to load it
+	# all into bash.
+	local base_addr=$(nm "$vmlinux" | awk "/ $name\$/ {print \$1}" | head -n1)
+
+	# Let's start doing the math to get the exact address into the
+	# symbol. First, strip out the symbol total length.
+	local expr=${symbol%/*}
+
+	# Now, replace the symbol name with the base address we found
+	# before.
+	expr=${expr/$name/0x$base_addr}
+
+	# Evaluate it to find the actual address
+	expr=$((expr))
+	local address=$(printf "%x\n" "$expr")
+
+	# Pass it to addr2line to get filename and line number
+        # Could get more than one result
+	local code=$(addr2line -i -e "$vmlinux" "$address")
+
+	# addr2line doesn't return a proper error code if it fails, so
+	# we detect it using the value it prints so that we could preserve
+	# the offset/size into the function and bail out
+	if [[ $code == "??:0" ]]; then
+		return
+	fi
+
+	# Strip out the base of the path
+	code=${code//$basepath/""}
+
+	# In the case of inlines, move everything to same line
+	code=${code//$'\n'/' '}
+
+	# Replace old address with pretty line numbers
+	symbol="$name ($code)"
+}
+
+handle_line() {
+	local words
+	# Tokenize
+	read -a words <<<"$1"
+
+	# Remove hex numbers. Do it ourselves until it happens in the
+	# kernel
+
+	# We need to know the index of the last element before we
+	# remove elements because arrays are sparse
+	local last=$(( ${#words[@]} - 1 ))
+
+	for i in "${!words[@]}"; do
+		# Remove the address
+		if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then
+			unset words[$i]
+		fi
+
+		# Format timestamps with tabs
+		if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then
+			unset words[$i]
+			words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}")
+		fi
+	done
+
+	# The symbol is the last element, process it
+	symbol=${words[$last]}
+	unset words[$last]
+	parse_symbol # modifies $symbol
+
+	# Add up the line number to the symbol
+	echo "${words[@]}" "$symbol"
+}
+
+while read line; do
+	# Let's see if we have an address in the line
+	if [[ $line =~ \[\<([^]]+)\>\]  ]]; then
+		# Translate address to line numbers
+		handle_line "$line"
+	else
+		# Nothing special in this line, show it as is
+		echo "$line"
+	fi
+done
-- 
1.7.10.4


<think>好的,我现在需要解决用户提出的编译错误问题。用户遇到的错误信息是关于链接器无法找到`base64_decode`函数的引用,导致编译失败。此外,还有一些关于文件操作的错误,比如无法创建目录、复制或删除文件。让我先仔细分析这些错误,然后一步步思考解决方案。 首先,用户提供的错误信息显示在链接阶段出现了`undefined reference to base64_decode`。这表明编译器在编译各个源文件时没有问题,但链接时找不到该函数的实现。通常,这种错误有几个常见原因:函数声明了但没有定义,或者定义所在的源文件没有被编译,或者链接时没有包含对应的库。 接下来,我需要考虑用户的项目结构。用户使用的是CMake,错误信息中提到了`libaspen_core.a`,这可能是一个静态库,而`JGUtility.cpp`是这个库的一部分。问题出在`decrypt`函数调用了`base64_decode`,但链接器找不到它的实现。因此,首先要确认`base64_decode`函数的来源。 可能的解决方案包括: 1. 检查是否包含了实现`base64_decode`的源文件或库。如果该函数是项目内的,确保对应的.cpp文件被添加到编译目标中。如果是外部库,需要确认链接时是否正确添加了库路径和库名称。 2. 确认函数签名是否一致。用户代码中调用的`base64_decode`是否与实际的函数声明一致?比如参数类型、命名空间等是否匹配。 3. 检查是否有命名空间的问题。如果`base64_decode`是在某个命名空间内定义的,而调用时没有使用该命名空间,会导致链接错误。 4. 如果是第三方库提供的函数,比如Crypto++,可能需要检查是否正确链接了该库,并且在代码中包含了正确的头文件。 此外,用户还遇到了一些文件操作相关的错误,比如`mkdir: cannot create directory ‘build_uft’: File exists`,这可能是因为构建目录已存在,而用户尝试再次创建导致的。建议在构建前删除旧的构建目录,或者让构建脚本自动处理目录创建。 接下来是文件复制和删除的错误,如`cp: cannot stat 'aspen_uft': No such file or directory`,这可能是因为编译生成的二进制文件`aspen_uft`没有成功生成,所以后续的复制和删除操作失败。这说明主要的编译错误(即链接错误)必须先解决,才能生成可执行文件,后续步骤才能成功。 现在需要分步骤解决这些问题: 首先解决链接错误`undefined reference to base64_decode`: 1. 确认`base64_decode`的实现是否存在。查看项目中的源文件是否有该函数的定义,或者是否来自某个库。例如,如果使用的是Crypto++库,可能需要检查是否正确使用了`Base64Decoder`类或相关函数。 2. 如果`base64_decode`是项目内的函数,检查对应的.cpp文件是否被添加到CMakeLists.txt的源文件列表中,或者是否被包含在静态库`libaspen_core.a`的构建中。例如,如果`base64_decode`定义在`Base64.cpp`中,但该文件没有被编译进库,就会导致链接错误。 3. 检查函数声明和定义是否一致。例如,函数是否被声明为`extern "C"`,或者在某个命名空间内,导致链接时名称修饰不一致。 如果用户使用的是第三方库的Base64解码功能,比如Crypto++,可能需要调整代码以使用库提供的接口。例如,Crypto++中的Base64解码通常通过`Base64Decoder`过滤器实现,而不是直接调用`base64_decode`函数。用户可能在代码中错误地调用了不存在的函数,或者没有正确使用库中的解码方法。 接下来处理文件操作相关的错误: 1. 构建目录已存在的问题:在构建前删除旧的`build_uft`目录,或者在CMake构建脚本中处理目录创建,避免重复创建。 2. 复制和删除错误:这些错误是因为编译失败导致的可执行文件`aspen_uft`未生成。因此,首要任务是解决编译错误,确保生成可执行文件后,这些操作自然会成功。 总结解决方案步骤: 1. 解决`base64_decode`的链接问题: a. 检查项目中是否存在该函数的实现,并确保对应的源文件被编译到库中。 b. 确认函数签名正确,包括参数类型、返回类型和命名空间。 c. 如果使用第三方库,确保正确包含头文件和链接库文件。 2. 调整CMakeLists.txt,确保所有必要的源文件和库都被正确包含和链接。 3. 处理构建目录的问题,确保构建前清理旧目录或允许覆盖。 4. 解决编译错误后,文件操作错误将自然消失。 可能的代码调整示例: 假设用户原本的`base64_decode`函数调用有误,应使用Crypto++的方式解码Base64字符串: 原错误代码可能类似: ```cpp std::string decoded = base64_decode(encoded_str); ``` 应改为使用Crypto++的Base64Decoder: ```cpp #include <cryptopp/filters.h> #include <cryptopp/base64.h> std::string decoded; CryptoPP::StringSource ss(encoded_str, true, new CryptoPP::Base64Decoder( new CryptoPP::StringSink(decoded) ) ); ``` 这样就不需要外部的`base64_decode`函数,而是使用库提供的功能,从而避免链接错误。 另外,确保CMakeLists.txt正确链接Crypto++库: ```cmake find_package(CryptoPP REQUIRED) target_link_libraries(aspen_uft PRIVATE CryptoPP::CryptoPP) ``` 最后,处理构建目录的问题,可以在构建脚本中添加清理步骤: ```bash rm -rf build_uft mkdir build_uft cd build_uft cmake .. make ``` 这样每次构建前都会删除旧目录,避免错误。 总结来说,用户需要检查`base64_decode`的实现来源,确保正确链接,并使用适当的库函数,同时处理构建脚本中的目录问题。</think>以下是对该编译错误的详细分析和解决方案: --- ### 错误分析 错误信息分为两部分: 1. **`undefined reference to base64_decode`** 这是典型的**链接错误**,表示编译器能找到函数声明(头文件已包含),但链接时找不到函数实现。可能原因: - 未实现 `base64_decode` 函数 - 实现该函数的源文件未编译到目标库(`libaspen_core.a`) - 未链接包含该函数的库 2. **文件操作错误(`mkdir/cp/rm`失败)** 这是编译失败后的后续错误,主因是链接错误导致可执行文件 `aspen_uft` 未生成,后续脚本无法复制或删除不存在的文件。 --- ### 解决方案 分两步解决: #### 第一步:解决 `base64_decode` 的链接问题 ##### 情况 1:函数由自定义代码实现 1. **检查函数实现是否存在** 确认项目中存在 `base64_decode` 的实现(如 `Base64.cpp`),且其签名与声明一致: ```cpp // 声明(.h文件) std::string base64_decode(const std::string& input, bool validate = true); // 实现(.cpp文件) std::string base64_decode(const std::string& input, bool validate) { // 实现代码... } ``` 2. **确保源文件加入编译** 在 `CMakeLists.txt` 中,将实现文件(如 `Base64.cpp`)添加到库或可执行目标的编译列表中: ```cmake add_library(aspen_core STATIC src/JGUtility.cpp src/Base64.cpp # 添加实现文件 ) ``` ##### 情况 2:函数来自第三方库(如 Crypto++) 若 `base64_decode` 是第三方库提供的函数: 1. **检查库的链接** 在 `CMakeLists.txt` 中确保链接到该库: ```cmake find_package(CryptoPP REQUIRED) # 假设使用 Crypto++ target_link_libraries(aspen_core PRIVATE CryptoPP::CryptoPP) ``` 2. **使用库的正确接口** Crypto++ 的 Base64 解码通常通过 `Base64Decoder` 类实现,而非直接调用 `base64_decode`。调整代码如下: ```cpp #include <cryptopp/base64.h> #include <cryptopp/filters.h> std::string decoded; CryptoPP::StringSource ss(encoded_str, true, new CryptoPP::Base64Decoder( new CryptoPP::StringSink(decoded) ) ); ``` --- #### 第二步:修复构建脚本错误 1. **清理旧构建目录** 在编译前删除已存在的 `build_uft` 目录: ```bash rm -rf build_uft # 确保脚本中有这一步 mkdir build_uft cd build_uft cmake .. make ``` 2. **仅在编译成功后复制文件** 在 `Makefile` 或脚本中,确保 `cp` 和 `rm` 操作仅在 `make` 成功后执行: ```bash if make; then cp aspen_uft /target/path else echo "Build failed" exit 1 fi ``` --- ### 完整修复流程示例 1. **修改 `JGUtility.cpp` 使用 Crypto++ 的 Base64 接口** ```cpp #include <cryptopp/base64.h> #include <cryptopp/filters.h> std::string aspen::decrypt(const std::string& data, const std::string& key, const std::string& iv) { // 假设 data 是 Base64 编码的密文 std::string ciphertext; CryptoPP::StringSource ss(data, true, new CryptoPP::Base64Decoder( new CryptoPP::StringSink(ciphertext) ) ); // 后续解密操作... } ``` 2. **更新 `CMakeLists.txt`** ```cmake find_package(CryptoPP REQUIRED) add_library(aspen_core STATIC src/JGUtility.cpp) target_link_libraries(aspen_core PRIVATE CryptoPP::CryptoPP) add_executable(aspen_uft main.cpp) target_link_libraries(aspen_uft PRIVATE aspen_core) ``` 3. **清理并重新构建** ```bash rm -rf build_uft mkdir build_uft && cd build_uft cmake .. && make ``` --- ### 关键注意事项 1. **符号一致性** 确保函数名、参数类型、命名空间在声明和实现中完全一致。 2. **依赖管理** 使用 `find_package` 或 `find_library` 正确配置第三方库路径。 3. **构建顺序** 清理旧构建产物可避免残留文件导致的问题。 --- ### 扩展建议 - **使用现代 CMake 实践** 显式指定目标依赖关系和属性: ```cmake target_include_directories(aspen_core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_compile_features(aspen_core PRIVATE cxx_std_17) ``` - **调试链接问题** 使用 `nm` 或 `objdump` 检查库文件是否包含目标符号: ```bash nm -gC libaspen_core.a | grep base64_decode ``` 通过上述步骤,应能解决链接错误和后续构建脚本问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值