├── LICENSE ├── README.md ├── RVBacktrace.py ├── SConscript ├── clean.py ├── doc └── 使用指南.pdf ├── figures ├── 1.png ├── 2.png └── logo.png ├── include └── rvbacktrace.h ├── package.yaml ├── src ├── rv_backtrace.c ├── rv_backtrace_fno.c └── rv_backtrace_fomit.c └── tools ├── example └── backtrace.txt ├── tracefunction.py ├── tracehtml.py ├── traceinfo.py └── tracepath.py /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![](figures/logo.png) 4 | 5 | ## 简介 6 | 7 | RVBacktrace是一个极简的RISC-V栈回溯组件。用于断言或异常等情况下的辅助调试。 8 | 9 | ![Contributors](https://img.shields.io/github/contributors/Yaochenger/RvBacktrace) 10 | ![Stars](https://img.shields.io/github/stars/Yaochenger/RvBacktrace?style=social) 11 | ![Issues](https://img.shields.io/github/issues/Yaochenger/RvBacktrace) 12 | ![Last Commit](https://img.shields.io/github/last-commit/Yaochenger/RvBacktrace) 13 | ![License](https://img.shields.io/github/license/Yaochenger/RvBacktrace) 14 | 15 | ## 组件功能 16 | 17 | ### 基础功能 18 | 19 | 1. 从调用位置栈回溯(支持支持FP模式栈回溯/软件栈回溯) 20 | 2. 命令查看当前`RT-Thread`系统所有线程的调用栈(依赖RT-Thread/仅支持FP模式栈回溯) 21 | 22 | ### 进阶功能 23 | 24 | 1. 定位栈溢出位置,从溢出位置进行栈回溯,(需要结合gcc的-fstack-protector-strong参数/须同时开启FP模式栈回溯) 25 | 26 | ## API介绍 27 | 28 | RVBacktrace组件仅包含一个面向用户的API,用户在需要的地方调用该函数可以进行从当前位置进行栈回溯,用户可将该API对接到断言函数或者在异常中调用,辅助调试。 29 | 30 | ```c 31 | void rvbacktrace(void); 32 | ``` 33 | 34 | ## 组件配置 35 | 36 | 组件支持两种方式栈回溯,当前默认使用配置简单的方式一,使用方式二请看下文的使用示例。 37 | 38 | 方式一:不添加编译参数,通过调用栈结构与指令类型进行栈回溯。 39 | 40 | 优点:不额外占用系统寄存器 缺点:增加代码空间,效率较方式一较低 41 | 42 | 方式二:通过添加编译参数的方式,基于FP寄存器进行栈回溯。 43 | 44 | 优点:几乎不增加代码空间 缺点:占用s0寄存器, 45 | 46 | ## 使用方法 47 | 48 | 方式一: 49 | 50 | 默认使用方式一,首先确认链接脚本中存在下述符号 51 | 52 | ```c 53 | extern char *__etext; 54 | extern char *__stext; 55 | ``` 56 | 57 | 若不存在,请在链接脚本中添加上述符号,__ etext位于代码段的开始,__stext位于代码段的末尾,以下是在链接脚本中添加上述符号的示例位置: 58 | 59 | ```stylus 60 | PROVIDE (__stext = .); 61 | .text (__vector_load_addr__ + __vector_ram_end__ - __vector_ram_start__) : { 62 | . = ALIGN(8); 63 | *(.text) 64 | *(.text*) 65 | *(.rodata) 66 | *(.rodata*) 67 | *(.srodata) 68 | *(.srodata*) 69 | ... 70 | /* section information for usbh class */ 71 | . = ALIGN(8); 72 | __usbh_class_info_start__ = .; 73 | KEEP(*(.usbh_class_info)) 74 | __usbh_class_info_end__ = .; 75 | 76 | } > XPI0 77 | 78 | .rel : { 79 | KEEP(*(.rel*)) 80 | } > XPI0 81 | 82 | PROVIDE (__etext = .); 83 | ``` 84 | 85 | 在期望栈回溯的地方插入rvbacktrace函数,示例如下: 86 | 87 | ```stylus 88 | void thread_entry(void *arg) 89 | { 90 | rt_thread_mdelay(500); 91 | extern void rvbacktrace(); 92 | rvbacktrace(); 93 | while(1){ 94 | app_led_write(0, APP_LED_ON); 95 | rt_thread_mdelay(500); 96 | app_led_write(0, APP_LED_OFF); 97 | rt_thread_mdelay(500); 98 | } 99 | } 100 | ``` 101 | 102 | 运行结果: 103 | 104 | ```stylus 105 | ---- RV_Backtrace Call Frame Start: ---- 106 | ###Please consider the value of ra as accurate and the value of sp as only for reference### 107 | ------------------------------Thread: led_th backtrace------------------------------ 108 | [0]Stack interval :[0x000000000108f868 - 0x000000000108f878] ra 0x000000008000cc6c pc 0x000000008000cc6a 109 | [1]Stack interval :[0x000000000108f878 - 0x000000000108f888] ra 0x000000008000cc6a pc 0x000000008000cc68 110 | [2]Stack interval :[0x000000000108f888 - 0x000000000108f898] ra 0x000000008000c92e pc 0x000000008000c92c 111 | [3]Stack interval :[0x000000000108f898 - 0x000000000108f8a0] ra 0x000000008001007e pc 0x000000008001007a 112 | End of stack backtracking 113 | 114 | addr2line -e rtthread.elf -a -f 8000cc6a 8000cc68 8000c92c 8001007a 115 | ---- RV_Backtrace Call Frame End:---- 116 | ``` 117 | 118 | 方式二: 119 | 120 | 首先在rvacktrace.h中定义BACKTRACE_USE_FP 121 | 122 | ```stylus 123 | #define BACKTRACE_USE_FP 124 | ``` 125 | 126 | 在C/C++以及汇编的编译选项下添加`-fno-omit-frame-pointer`编译参数,RT-Studio中的添加方式示例: 127 | 128 | ![](figures/1.png) 129 | 130 | 在期望栈回溯的地方插入rvbacktrace函数,示例如下: 131 | 132 | ```stylus 133 | void thread_entry(void *arg) 134 | { 135 | rt_thread_mdelay(500); 136 | extern void rvbacktrace(); 137 | rvbacktrace(); 138 | while(1){ 139 | app_led_write(0, APP_LED_ON); 140 | rt_thread_mdelay(500); 141 | app_led_write(0, APP_LED_OFF); 142 | rt_thread_mdelay(500); 143 | } 144 | } 145 | ``` 146 | 147 | 运行结果: 148 | 149 | ```stylus 150 | ---- RV_Backtrace Call Frame Start: ---- 151 | ###Please consider the value of ra as accurate and the value of sp as only for reference### 152 | ------------------------------Thread: led_th backtrace------------------------------ 153 | Current Thread Name: led_th 154 | [0]Stack interval :[0x0000000001090388 - 0x0000000001090398] ra 0x000000008000d114 pc 0x000000008000d110 155 | [1]Stack interval :[0x0000000001090398 - 0x00000000010903a8] ra 0x000000008000cfba pc 0x000000008000cfb6 156 | [2]Stack interval :[0x00000000010903a8 - 0x00000000010903b8] ra 0x000000008001095a pc 0x0000000080010956 157 | [3]Stack interval :[0x00000000010903b8 - 0x00000000deadbeef] ra 0x0000000001086a7c pc 0x0000000001086a78 158 | 159 | addr2line -e rtthread.elf -a -f 8000d110 8000cfb6 80010956 1086a78 160 | ---- RV_Backtrace Call Frame End:---- 161 | ``` 162 | 163 | 上述信息便是当前的栈回溯信息,除此之外组件输出addr2line工具支持的栈回溯命令,使用addr2line命令运行下述代码即可查看函数调用栈与函数符号。 164 | 165 | ```stylus 166 | addr2line -e rtthread.elf -a -f 8000d110 8000cfb6 80010956 1086a78 167 | ``` 168 | 169 | 执行上述命令的示例结果如下图所示: 170 | 171 | ![](figures/2.png) 172 | 173 | 方式二支持对当前系统所有的线程进行栈回溯,在rvacktrace.h中定义BACKTRACE_ALL即可输出当前系统各线程的调用栈。 174 | 175 | ```stylus 176 | #define BACKTRACE_ALL 177 | ``` 178 | 179 | 重新执行上述代码,运行结果: 180 | 181 | ```stylus 182 | ---- RV_Backtrace Call Frame Start: ---- 183 | ###Please consider the value of ra as accurate and the value of sp as only for reference### 184 | ------------------------------Thread: led_th backtrace------------------------------ 185 | Current Thread Name: led_th 186 | [0]Stack interval :[0x0000000001090390 - 0x00000000010903a0] ra 0x000000008000d26e pc 0x000000008000d26a 187 | [1]Stack interval :[0x00000000010903a0 - 0x00000000010903b0] ra 0x000000008000cfba pc 0x000000008000cfb6 188 | [2]Stack interval :[0x00000000010903b0 - 0x00000000010903c0] ra 0x0000000080010ac2 pc 0x0000000080010abe 189 | [3]Stack interval :[0x00000000010903c0 - 0x00000000deadbeef] ra 0x0000000001086a7c pc 0x0000000001086a78 190 | 191 | addr2line -e rtthread.elf -a -f 8000d26a 8000cfb6 80010abe 1086a78 192 | 193 | ------------------------------Thread: tshell backtrace------------------------------ 194 | [1]Thread Name: tshell 195 | [0]Stack interval :[0x000000000108fea0 - 0x000000000108fed0] ra 0x0000000001083f5c pc 0x0000000001083f58 196 | [1]Stack interval :[0x000000000108fed0 - 0x000000000108fee0] ra 0x0000000001084782 pc 0x000000000108477e 197 | [2]Stack interval :[0x000000000108fee0 - 0x000000000108ff00] ra 0x0000000080009d80 pc 0x0000000080009d7c 198 | [3]Stack interval :[0x000000000108ff00 - 0x000000000108ff10] ra 0x0000000080009eec pc 0x0000000080009ee8 199 | [4]Stack interval :[0x000000000108ff10 - 0x00000000deadbeef] ra 0x0000000001086a7c pc 0x0000000001086a78 200 | 201 | addr2line -e rtthread.elf -a -f 1083f58 108477e 80009d7c 80009ee8 1086a78 202 | ------------------------------Thread: tidle0 backtrace------------------------------ 203 | [2]Thread Name: tidle0 204 | [0]Stack interval :[0x000000000108bf70 - 0x00000000deadbeef] ra 0x0000000001086a7c pc 0x0000000001086a78 205 | 206 | addr2line -e rtthread.elf -a -f 1086a78 207 | ------------------------------Thread: timer backtrace------------------------------ 208 | [3]Thread Name: timer 209 | [0]Stack interval :[0x000000000108c408 - 0x000000000108c428] ra 0x0000000001088636 pc 0x0000000001088632 210 | [1]Stack interval :[0x000000000108c428 - 0x00000000deadbeef] ra 0x0000000001086a7c pc 0x0000000001086a78 211 | 212 | addr2line -e rtthread.elf -a -f 1088632 1086a78 213 | ------------------------------Thread: main backtrace------------------------------ 214 | [4]Thread Name: main 215 | [0]Stack interval :[0x000000000108ebd8 - 0x000000000108ec08] ra 0x0000000001086f68 pc 0x0000000001086f64 216 | [1]Stack interval :[0x000000000108ec08 - 0x000000000108ec18] ra 0x0000000001087096 pc 0x0000000001087092 217 | [2]Stack interval :[0x000000000108ec18 - 0x000000000108ec28] ra 0x0000000080010b84 pc 0x0000000080010b80 218 | [3]Stack interval :[0x000000000108ec28 - 0x000000000108ec38] ra 0x0000000080006890 pc 0x000000008000688c 219 | [4]Stack interval :[0x000000000108ec38 - 0x00000000deadbeef] ra 0x0000000001086a7c pc 0x0000000001086a78 220 | 221 | addr2line -e rtthread.elf -a -f 1086f64 1087092 80010b80 8000688c 1086a78 222 | Thread Total Num: 5 223 | ---- RV_Backtrace Call Frame End:---- 224 | ``` 225 | 226 | 该模式用于tshell线程可以正常执行时打印输出当前系统所有线程的调用栈,用于辅助分析死锁,线程异常挂起等现象 227 | 228 | ## 进阶功能示例 229 | 230 | ### 1.栈溢出检测 231 | 232 | 在rvbacktrace.h文件中定义下述宏: 233 | 234 | ```stylus 235 | #define BACKTRACE_USE_FP 236 | #define BACKTRACE_FSTACK_PROTECT 237 | ``` 238 | 239 | 在C/C++并编译参数中添加`-fstack-protector-strong`,在连接参数中添加`-Wl,--wrap,_exit`,最后添加下述示例代码编译运行 240 | 241 | ```stylus 242 | int main(void) 243 | { 244 | char array[5] = {0}; 245 | rt_strcpy(array, "stack leak test"); 246 | 247 | return 0; 248 | } 249 | ``` 250 | 251 | 运行结果: 252 | 253 | ```stylus 254 | *** stack smashing detected ***: terminated 255 | 256 | ---- RV_Backtrace Call Frame Start: ---- 257 | ###Please consider the value of ra as accurate and the value of sp as only for reference### 258 | ------------------------------Thread: main backtrace------------------------------ 259 | Current Thread Name: main 260 | [0]Stack interval :[0x000000000108ef38 - 0x000000000108ef48] ra 0x000000008000d450 pc 0x000000008000d44c 261 | [1]Stack interval :[0x000000000108ef48 - 0x000000000108ef58] ra 0x000000008000d2bc pc 0x000000008000d2b8 262 | [2]Stack interval :[0x000000000108ef58 - 0x000000000108ef68] ra 0x000000008000d368 pc 0x000000008000d364 263 | [3]Stack interval :[0x000000000108ef68 - 0x000000000108efc8] ra 0x0000000080012224 pc 0x0000000080012220 264 | [4]Stack interval :[0x000000000108efc8 - 0x000000000108efd8] ra 0x0000000080006890 pc 0x000000008000688c 265 | [5]Stack interval :[0x000000000108efd8 - 0x00000000deadbeef] ra 0x0000000001086bc4 pc 0x0000000001086bc0 266 | 267 | addr2line -e rtthread.elf -a -f 8000d44c 8000d2b8 8000d364 80012220 8000688c 1086bc0 268 | ---- RV_Backtrace Call Frame End:---- 269 | 270 | [W/stdlib] thread:main exit:127! 271 | ``` 272 | 273 | 通过上述信息便可以定位到栈溢出的线程与代码位置。 274 | 275 | ## 验证平台 276 | 277 | | 芯片/内核 | 验证结果 | 278 | | :---------------: | :------: | 279 | | D1/XuanTian C906 | pass | 280 | | CH32V307/RISC-V4F | pass | 281 | | HPM6750/Andes D45 | pass | 282 | | QEMU RISCV64 VIRT | pass | 283 | | XuanTie E906 | pass | 284 | 285 | ## 参考链接 286 | 287 | 上手说明:https://club.rt-thread.org/ask/article/64bfe06feb7b3e29.html 288 | -------------------------------------------------------------------------------- /RVBacktrace.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import time 4 | 5 | def check_file_modified(file_path, last_modified_time): 6 | """检查文件是否自上次检查后被修改过""" 7 | if not os.path.exists(file_path): 8 | return False # 如果文件不存在,则认为没有被修改 9 | current_modified_time = os.path.getmtime(file_path) 10 | return current_modified_time != last_modified_time 11 | 12 | def read_info_path_from_file(file_path): 13 | """从文件中读取info_path字段的值,并尝试将其转换为绝对路径(如果可能)""" 14 | try: 15 | with open(file_path, 'r') as file: 16 | for line in file: 17 | if line.startswith('info_path ='): 18 | value = line.split('=', 1)[1].strip() 19 | # 去除字符串两端的引号(如果有) 20 | cleaned_value = value.strip("'\"") 21 | # 尝试将路径转换为绝对路径(如果它是相对路径) 22 | return os.path.abspath(cleaned_value) if not os.path.isabs(cleaned_value) else cleaned_value 23 | return None # 如果没有找到info_path,则返回None 24 | except FileNotFoundError: 25 | print(f"文件 {file_path} 未找到。") 26 | return None 27 | 28 | def execute_scripts(tools_dir): 29 | """执行指定目录下的脚本""" 30 | scripts = ['tracepath.py', 'tracefunction.py', 'traceinfo.py', 'tracehtml.py'] 31 | for script in scripts: 32 | full_path = os.path.join(tools_dir, script) 33 | subprocess.run(['python', full_path], check=True) 34 | 35 | def open_html_file(html_file): 36 | """尝试在默认浏览器中打开HTML文件""" 37 | try: 38 | subprocess.run(['start', html_file], shell=True, check=True) 39 | except subprocess.CalledProcessError: 40 | print(f"Failed to open {html_file} with the default browser.") 41 | 42 | def main(): 43 | script_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本的目录 44 | tools_dir = os.path.join(script_dir, 'tools') 45 | html_dir = os.path.join(tools_dir, 'html') 46 | html_file = os.path.join(html_dir, 'rvbacktrace.html') 47 | 48 | # 确保html目录存在 49 | os.makedirs(html_dir, exist_ok=True) 50 | 51 | # 执行脚本 52 | execute_scripts(tools_dir) 53 | 54 | # 尝试打开HTML文件 55 | open_html_file(html_file) 56 | 57 | # 读取info_path并监控文件更改 58 | path_txt_file = os.path.join(script_dir, 'tools', 'obj', 'path.txt') 59 | info_path = read_info_path_from_file(path_txt_file) 60 | print("\n[RV] 修改栈回溯信息文本后,脚本将自动重新生成栈回溯HTML文件。") 61 | print("--输入Ctrl+C退出脚本--\n") 62 | if info_path: 63 | last_modified_time = os.path.getmtime(info_path) 64 | 65 | while True: 66 | time.sleep(1) # 每秒检查一次 67 | 68 | if check_file_modified(info_path, last_modified_time): 69 | print(f"[RV] 栈回溯文件 {info_path} 已被修改。") 70 | last_modified_time = os.path.getmtime(info_path) 71 | print("[RV] 重新生成栈回溯HTML文件...") 72 | main() # 递归调用main(),但请注意潜在的堆栈溢出 73 | break # 如果不希望无限递归,可以在检测到更改后退出循环(但这将停止监控) 74 | 75 | else: 76 | print("无法从文件中读取info_path或文件不存在。") 77 | 78 | if __name__ == "__main__": 79 | try: 80 | main() 81 | except RecursionError: 82 | print("发生递归错误,可能是由于文件被频繁修改导致的无限递归调用。") 83 | except Exception as e: 84 | print(f"发生错误:{e}") -------------------------------------------------------------------------------- /SConscript: -------------------------------------------------------------------------------- 1 | from building import * 2 | 3 | src = Glob('*.c') 4 | src += Glob('src/*.c') 5 | cwd = GetCurrentDir() 6 | inc = [os.path.join(cwd, 'include')] 7 | 8 | group = DefineGroup('RV_Backtrace', src, depend = [''], CPPPATH = inc) 9 | 10 | Return('group') 11 | -------------------------------------------------------------------------------- /clean.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | # 获取当前脚本的目录 5 | current_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | # 构造tools文件夹的路径 8 | tools_dir = os.path.join(current_dir, 'tools') 9 | 10 | # 构造html和obj文件夹的路径 11 | html_dir = os.path.join(tools_dir, 'html') 12 | obj_dir = os.path.join(tools_dir, 'obj') 13 | 14 | # 检查这些文件夹是否存在,如果存在则删除 15 | if os.path.exists(html_dir): 16 | shutil.rmtree(html_dir) 17 | print(f"Deleted {html_dir}") 18 | else: 19 | print(f"{html_dir} does not exist.") 20 | 21 | if os.path.exists(obj_dir): 22 | shutil.rmtree(obj_dir) 23 | print(f"Deleted {obj_dir}") 24 | else: 25 | print(f"{obj_dir} does not exist.") 26 | 27 | # 如果tools文件夹为空(可选),你也可以选择删除它 28 | # 注意:这将在html和obj文件夹被删除且tools内无其他文件时执行 29 | if os.path.exists(tools_dir) and not os.listdir(tools_dir): 30 | shutil.rmtree(tools_dir) 31 | print(f"Deleted {tools_dir} because it was empty.") 32 | else: 33 | print(f"{tools_dir} still contains files or is not empty.") -------------------------------------------------------------------------------- /doc/使用指南.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yaochenger/RvBacktrace/e38908548baf04acf9046c2691caa6567bd143b2/doc/使用指南.pdf -------------------------------------------------------------------------------- /figures/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yaochenger/RvBacktrace/e38908548baf04acf9046c2691caa6567bd143b2/figures/1.png -------------------------------------------------------------------------------- /figures/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yaochenger/RvBacktrace/e38908548baf04acf9046c2691caa6567bd143b2/figures/2.png -------------------------------------------------------------------------------- /figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yaochenger/RvBacktrace/e38908548baf04acf9046c2691caa6567bd143b2/figures/logo.png -------------------------------------------------------------------------------- /include/rvbacktrace.h: -------------------------------------------------------------------------------- 1 | #ifndef RV_BACKTRANCE_H 2 | #define RV_BACKTRANCE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "rtthread.h" 8 | 9 | /* User Configure */ 10 | // #define BACKTRACE_USE_FP // To enable this option, add the [-fno-omit-frame-pointer] option to ASM,C/C++. 11 | // #define BACKTRACE_ALL // Before enabling this option, enable the BACKTRACE_USE_FP / Outputs the stack of all threads 12 | // #define BACKTRACE_FSTACK_PROTECT // To enable this option, add the [-fstack-protector-strong] option to ASM,C/C++, add [-Wl,--wrap,_exit] flag to link option. 13 | #define BACKTRACE_PRINTF rt_kprintf // Printf function to print stack back information 14 | #define rv_memcpy rt_memcpy // Memory copy function 为了支持非对齐访问 15 | 16 | #define BACKTRACE_TEXT_START __stext 17 | #define BACKTRACE_TEXT_END __etext 18 | #define BACKTRACE_ELF_NAME "rtthread.elf" // The name of the ELF file generated by the linker script, which is used to generate the addr2line command 19 | 20 | /* Backtrace All Threads */ 21 | #if defined(BACKTRACE_USE_FP) && defined(BACKTRACE_ALL) 22 | #define BACKTRACE_ALL_THREAD 23 | #define BACKTRACE_FP_POS (8) 24 | #endif /* BACKTRACE_USE_FP && defined BACKTRACE_ALL */ 25 | 26 | /* Backtrace Printf */ 27 | #if !defined(BACKTRACE_PRINTF) 28 | #define BACKTRACE_PRINTF printf 29 | #endif /* BACKTRACE_PRINTF */ 30 | 31 | /* User Parameter */ 32 | #define STACK_FRAME_LEN (10) 33 | #define STACK_BUFFER_LEN (100) 34 | 35 | #if __riscv_xlen == 32 36 | #define BACKTRACE_LEN 8 37 | #endif 38 | 39 | #if __riscv_xlen == 64 40 | #define BACKTRACE_LEN 16 41 | #endif 42 | 43 | #ifdef BACKTRACE_USE_FP 44 | #define RV_BACKTRACE_USE_FP 45 | #endif /* BACKTRACE_USE_FP */ 46 | 47 | struct stackframe 48 | { 49 | unsigned long s_fp; // frame pointer 50 | unsigned long s_ra; // return address 51 | }; 52 | 53 | void rvbacktrace(void); // backtrace function for usr 54 | void rvbacktrace_fno(void); 55 | int rvbacktrace_fomit(void); 56 | void rvbacktrace_addr2line(uint32_t *frame); 57 | void rvbacktrace_info(uint32_t *uSP, uint32_t *uPC); 58 | #endif /* RV_BACKTRANCE_H */ 59 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | 2 | ## 第一部分: 基础信息 3 | name: rv_backtrace # <必选项> 包名称 (符合C语言变量命名规则),长度少于等于64字节 4 | version: develop # <必选项> 组件版本号 5 | description: rv_backtrace # <必选项> 建议至少20字以上 6 | type: common # <必选项> 组件类型,为:solution, chip, board, common, sdk 7 | tag: 核心模块 # <可选项> 组件分类,缺省值: '' 8 | keywords: # <可选项> 标签,会影响到组件被搜索的效果,合理的标签很重要 9 | - base 10 | license: Apache license v2.0 # <可选项> 源代码的许可证,要确保所有代码、文件的许可证不冲突。如:MIT,Apache license v2.0,BSD 11 | 12 | ## 第二部分:依赖信息 13 | # 指定该组件依赖的组件及版本 14 | # sdk_chip: # <可选项> 该组件依赖sdk组件,合理的依赖才能保证组件能编译、使用 15 | # - sdk_chip_csky_dummy: v7.4.0 16 | # - sdk_chip_riscv_dummy: v7.4.0 17 | depends: # <可选项> 该组件依赖其他的组件,合理的依赖才能保证组件能编译、使用 18 | # - devices: develop 19 | 20 | ## 第四部分:编译连接信息 21 | # build_config: # <可选项> 编译配置项 22 | # include: # <可选项> 编译时,影响编译器的-I 参数 ,全局有效 23 | # - src # include 只能是该软件包下的目录,不能使用外部目录 24 | # internal_include: # <可选项> 编译时,影响编译器的-I 参数 ,组件内有效 25 | # - include 26 | # cflag: '' # <可选项> C 编译器所需要要的编译参数 27 | # cxxflag: '' # <可选项> CXX 编译器所需要要的编译参数 28 | # asmflag: '' # <可选项> 汇编器所需要要参数 29 | # define: # <可选项> 宏定义, 增加编译器的-D 选项,如: 30 | # XXX: 1 # -DXXX=1 31 | # AAA: 1 # -DAAA 32 | # STR: "abc" # -DSTR="abc" 33 | # libs: # 该组件中支持的二进制静态库,如:libxxx.a, libyyy.a 34 | # - xxx # -lxxx 35 | # - yyy # -lyyy 36 | # libpath: # 指定静态库所在的路径(相对于该组件路径) 37 | # - libs # -Llibs 38 | build_config: 39 | include: 40 | - include 41 | 42 | # source_file: # <可选项> 指定参与编译的源代码文件,支持通配符,采用相对路径 43 | # - src/*.c # 例:组件 src 目录下所有的扩展名为 c 的源代码文件 44 | source_file: 45 | - src/*.c 46 | - src/*.S 47 | 48 | ## 第五部分:配置信息 49 | # def_config: # 组件的可配置项 50 | # CONFIG_DEBUG: y 51 | # CONFIG_PARAM_NOT_CHECK: y 52 | # CONFIG_CLI: y 53 | def_config: 54 | CONFIG_RV_BACKTRACE: y 55 | 56 | ## 第六部分:安装信息 57 | # install: 58 | # - dest: include/ # 安装的目的路径 dest是相对路径,通常是相对于YoC SDK 安装目录 59 | # source: # 安装源列表 60 | # - src/*.h # 支持通配符,相对路径 61 | 62 | -------------------------------------------------------------------------------- /src/rv_backtrace.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006-2024, RT-Thread Development Team 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2023-11-23 WangShun the first version 9 | * 2024-08-30 WangShun add addr2line function 10 | */ 11 | 12 | #include "rvbacktrace.h" 13 | 14 | unsigned int rvstack_frame[STACK_FRAME_LEN]; // stack frame 15 | unsigned int rvstack_frame_len; // stack frame len 16 | 17 | extern unsigned int rvstack_frame_len; // stack frame len 18 | void rvbacktrace(void) 19 | { 20 | #ifdef RV_BACKTRACE_USE_FP 21 | rvbacktrace_fno(); 22 | #else 23 | rvbacktrace_fomit(); 24 | #endif /* RV_BACKTRACE_USE_FP */ 25 | } 26 | 27 | void rvbacktrace_addr2line(uint32_t *frame) 28 | { 29 | char buffer[STACK_BUFFER_LEN]; 30 | int offset = 0; 31 | 32 | for (int i = 0; i < rvstack_frame_len; i++) 33 | { 34 | #if __riscv_xlen == 64 35 | uint64_t addr = frame[i] & 0x00000000ffffffff; // 按位与操作去掉符号位 36 | offset += snprintf(buffer + offset, STACK_BUFFER_LEN - offset, "%lx ", addr); 37 | #else 38 | offset += snprintf(buffer + offset, STACK_BUFFER_LEN - offset, "%lx ", frame[i]); 39 | #endif 40 | if (offset >= STACK_BUFFER_LEN) 41 | break; 42 | } 43 | BACKTRACE_PRINTF("\naddr2line -e %s -a -f %s\n", BACKTRACE_ELF_NAME,buffer); 44 | } 45 | 46 | #if defined (BACKTRACE_FSTACK_PROTECT) 47 | __attribute__ ((noreturn)) void __wrap__exit(int status) 48 | { 49 | extern void rvbacktrace(void); 50 | extern void __rt_libc_exit(int status); 51 | rvbacktrace(); 52 | __rt_libc_exit(status); 53 | while (1); 54 | } 55 | #endif /* BACKTRACE_FSTACK_PROTECT */ 56 | 57 | #ifdef RT_USING_FINSH 58 | #include 59 | 60 | long rvb_test(int argc, char **argv) { 61 | int x, y, z; 62 | 63 | if (argc < 2) 64 | { 65 | BACKTRACE_PRINTF("Please input 'rvb_test ' \n"); 66 | return 0; 67 | } 68 | 69 | if (!strcmp(argv[1], "DIVBYZERO")) 70 | { 71 | x = 10; 72 | y = rt_strlen(""); 73 | z = x / y; 74 | BACKTRACE_PRINTF("z:%d\n", z); 75 | return 0; 76 | } 77 | else if (!strcmp(argv[1], "UNALIGNED")) 78 | { 79 | volatile int * p; 80 | volatile int value; 81 | 82 | p = (int *) 0x00; 83 | value = *p; 84 | BACKTRACE_PRINTF("addr:0x%02X value:0x%08X\r\n", (int) p, value); 85 | 86 | p = (int *) 0x04; 87 | value = *p; 88 | BACKTRACE_PRINTF("addr:0x%02X value:0x%08X\r\n", (int) p, value); 89 | 90 | p = (int *) 0x03; 91 | value = *p; 92 | BACKTRACE_PRINTF("addr:0x%02X value:0x%08X\r\n", (int) p, value); 93 | 94 | return 0; 95 | } 96 | else if (!strcmp(argv[1], "ASSERT")) 97 | { 98 | RT_ASSERT(0); 99 | } 100 | else if (!strcmp(argv[1], "HARDFAULT")) 101 | { 102 | uint32_t a[1] = {0x1234}; 103 | typedef void (*func)(void); 104 | func f = (func)&a[0]; 105 | f(); 106 | } 107 | return 0; 108 | } 109 | MSH_CMD_EXPORT(rvb_test, rvb_test: rvb_test ); 110 | 111 | rt_err_t exception_hook(void *context) { 112 | volatile uint8_t _continue = 1; 113 | 114 | rvbacktrace(); 115 | while (_continue == 1); 116 | 117 | return RT_EOK; 118 | } 119 | 120 | void assert_hook(const char* ex, const char* func, rt_size_t line) { 121 | volatile uint8_t _continue = 1; 122 | 123 | rvbacktrace(); 124 | while (_continue == 1); 125 | } 126 | 127 | int rt_cm_backtrace_init(void) { 128 | static rt_bool_t is_init = RT_FALSE; 129 | 130 | if (is_init) 131 | { 132 | return 0; 133 | } 134 | 135 | //暂时在RISCV平台没有对应的接口,在trap_entry 或者hardfault中断中自行添加RVBACKTRACE接口函数 136 | //rt_hw_exception_install(exception_hook); 137 | 138 | rt_assert_set_hook(assert_hook); 139 | 140 | is_init = RT_TRUE; 141 | return 0; 142 | } 143 | INIT_DEVICE_EXPORT(rt_cm_backtrace_init); 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /src/rv_backtrace_fno.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006-2024, RT-Thread Development Team 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2023-11-21 WangShun the first version 9 | * 2024-08-30 WangShun add addr2line function 10 | * 2024-09-19 WangShun improvement of function formal parameter 11 | * 12 | */ 13 | 14 | #include "rvbacktrace.h" 15 | #ifdef BACKTRACE_USE_FP 16 | static uint32_t _rt_susrstack; 17 | static uint32_t _rt_eusrstack; 18 | static rt_thread_t _backtrace_thread; 19 | static rt_thread_t _backtrace_threadn; 20 | static rt_object_t * thread_object_table = RT_NULL; 21 | extern unsigned int rvstack_frame[STACK_FRAME_LEN]; // stack frame 22 | extern unsigned int rvstack_frame_len; // stack frame len 23 | 24 | static void walk_stackframe() 25 | { 26 | uint32_t num = 0; 27 | _backtrace_thread = rt_thread_self(); // get current thread 28 | _rt_susrstack = (uint32_t)(uintptr_t)_backtrace_thread->stack_addr; // stack start address 29 | _rt_eusrstack = (uint32_t)(uintptr_t)(_backtrace_thread->stack_addr + _backtrace_thread->stack_size); // stack end address 30 | 31 | unsigned long sp, fp, ra, pc; // stack pointer, frame pointer, return address, program counter 32 | struct stackframe *frame; 33 | 34 | const register unsigned long current_sp __asm__("sp"); // get current stack pointer 35 | sp = current_sp; 36 | fp = (unsigned long)__builtin_frame_address(0); // get current frame pointer 37 | BACKTRACE_PRINTF("Current Thread Name: %s \n", _backtrace_thread->parent.name); 38 | while (1) 39 | { 40 | frame = (struct stackframe *)(fp - BACKTRACE_LEN); // get frame pointer 41 | 42 | if ((uint32_t *)frame > (uint32_t *)(uintptr_t)_rt_eusrstack) 43 | { 44 | rvstack_frame_len = num; 45 | return; 46 | } 47 | 48 | sp = fp; // get stack pointer 49 | fp = frame->s_fp; // get frame pointer 50 | ra = frame->s_ra; // get return address 51 | pc = frame->s_ra - 4; // get program counter 52 | 53 | // print stack interval, return address, program counter 54 | BACKTRACE_PRINTF("[%d]Stack interval :[0x%016lx - 0x%016lx] ra 0x%016lx pc 0x%016lx\n", num, sp, fp, ra, pc); 55 | rvstack_frame[num] = pc; // save stack frame address 56 | num++; 57 | } 58 | } 59 | 60 | #if defined(BACKTRACE_ALL) 61 | static void walk_stackframe_all(void) 62 | { 63 | uint32_t num = 0, i = 0; 64 | int thread_object_len = 0; 65 | unsigned long sp, fp, ra, pc; // stack pointer, frame pointer, return address, program counter 66 | struct stackframe *frame; 67 | 68 | thread_object_len = rt_object_get_length(RT_Object_Class_Thread); 69 | if (thread_object_len == RT_NULL) 70 | { 71 | return; 72 | } 73 | 74 | thread_object_table = (rt_object_t *) rt_malloc((sizeof(rt_object_t) * thread_object_len)); 75 | RT_ASSERT(thread_object_table != RT_NULL); 76 | 77 | rt_object_get_pointers(RT_Object_Class_Thread, (rt_object_t *) thread_object_table, thread_object_len); 78 | 79 | for (i = 0; i < thread_object_len; i++) 80 | { 81 | _backtrace_threadn = (rt_thread_t) thread_object_table[i]; 82 | 83 | if (_backtrace_threadn == (rt_thread_t) rt_thread_self()) 84 | { 85 | continue; 86 | } 87 | 88 | _rt_susrstack = (uint32_t) (uintptr_t) _backtrace_threadn->stack_addr; // stack start address 89 | _rt_eusrstack = (uint32_t) (uintptr_t) (_backtrace_threadn->stack_addr + _backtrace_threadn->stack_size); // stack end address 90 | 91 | BACKTRACE_PRINTF("------------------------------Thread: %s backtrace------------------------------\r\n", 92 | _backtrace_threadn->parent.name); 93 | BACKTRACE_PRINTF("[%d]Thread Name: %s \n", i, _backtrace_threadn->parent.name); 94 | sp = (unsigned long) _backtrace_threadn->sp; 95 | fp = ((rt_ubase_t *) (_backtrace_threadn->sp))[BACKTRACE_FP_POS]; // get current frame pointer 96 | while (1) 97 | { 98 | frame = (struct stackframe *) (fp - BACKTRACE_LEN); // get frame pointer 99 | 100 | if ((uint32_t *) frame > (uint32_t *) (uintptr_t) _rt_eusrstack) 101 | { 102 | rvstack_frame_len = num; 103 | rvbacktrace_addr2line((uint32_t *) &rvstack_frame[0]); 104 | num = 0; 105 | break; 106 | } 107 | 108 | sp = fp; // get stack pointer 109 | fp = frame->s_fp; // get frame pointer 110 | ra = frame->s_ra; // get return address 111 | pc = frame->s_ra - 4; // get program counter 112 | 113 | // print stack interval, return address, program counter 114 | BACKTRACE_PRINTF("[%d]Stack interval :[0x%016lx - 0x%016lx] ra 0x%016lx pc 0x%016lx\n", num, sp, fp, ra, pc); 115 | rvstack_frame[num] = pc; // save stack frame address 116 | num++; 117 | } 118 | } 119 | BACKTRACE_PRINTF("Thread Total Num: %d\n", thread_object_len); 120 | } 121 | #endif /* BACKTRACE_ALL */ 122 | 123 | // backtrace function 124 | void rvbacktrace_fno(void) 125 | { 126 | BACKTRACE_PRINTF("\r\n---- RV_Backtrace Call Frame Start: ----\r\n"); 127 | BACKTRACE_PRINTF("###Please consider the value of ra as accurate and the value of sp as only for reference###\n"); 128 | BACKTRACE_PRINTF("------------------------------Thread: %s backtrace------------------------------\r\n", ((rt_thread_t)rt_thread_self())->parent.name); 129 | walk_stackframe(); 130 | rvbacktrace_addr2line((uint32_t *)&rvstack_frame[0]); // addr2line function 131 | #if defined (BACKTRACE_ALL_THREAD) 132 | BACKTRACE_PRINTF("\r\n"); 133 | walk_stackframe_all(); 134 | #endif /* BACKTRACE_ALL_THREAD */ 135 | BACKTRACE_PRINTF("---- RV_Backtrace Call Frame End:----\r\n"); 136 | BACKTRACE_PRINTF("\r\n"); 137 | } 138 | #ifdef RT_USING_FINSH 139 | void rv_backtrace_func(void) 140 | { 141 | rvbacktrace_fno(); 142 | } 143 | MSH_CMD_EXPORT_ALIAS(rv_backtrace_func, rv_backtrace_all, backtrace all threads); 144 | 145 | 146 | #endif 147 | 148 | #endif /* BACKTRACE_USE_FP */ 149 | -------------------------------------------------------------------------------- /src/rv_backtrace_fomit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Change Logs: 3 | * Date Author Notes 4 | * 2023-11-23 WangShun the first version 5 | * 2024-09-19 WangShun support rv32 6 | * 2025-04-13 Supper Thomas 修复汇编指令的异常,立即数改为8字节对齐方便适配RISCV32平台 7 | */ 8 | 9 | #include "rvbacktrace.h" 10 | 11 | /* Please check that the following symbols are defined in the linked scripts !*/ 12 | /* If not, define the following symbols at the beginning and end of the text segment */ 13 | extern char *BACKTRACE_TEXT_END; 14 | extern char *BACKTRACE_TEXT_START; 15 | 16 | extern unsigned int rvstack_frame[STACK_FRAME_LEN]; // stack frame 17 | extern unsigned int rvstack_frame_len; // stack frame len 18 | 19 | static int lvl; 20 | 21 | #define BT_CHK_PC_AVAIL(pc) ((uintptr_t)(pc) < (uintptr_t)(&BACKTRACE_TEXT_END) \ 22 | && (uintptr_t)(pc) > (uintptr_t)(&BACKTRACE_TEXT_START)) 23 | 24 | #define BT_FUNC_LIMIT 0x2000 25 | #define BT_LVL_LIMIT 64 26 | 27 | #define BT_PC2ADDR(pc) ((char *)(((uintptr_t)(pc)))) 28 | 29 | /* get framesize from c ins32 */ 30 | static int riscv_backtrace_framesize_get1(unsigned int inst) 31 | { 32 | unsigned int imm = 0; 33 | /* addi sp, sp, -im 34 | * example 35 | * d1010113 addi sp,sp,-752 36 | * from spec addi FROM https://riscv.github.io/riscv-isa-manual/snapshot/unprivileged/#_integer_register_immediate_instructions 37 | * bit[31:20] = imm[11:0] 38 | * bit[19:15] = 00010 39 | * bit[14:12] = 000 40 | * bit[11:7] = 00010 41 | * bit[6:0] = 0010011 42 | */ 43 | if ((inst & 0x800FFFFF) == 0x80010113) { 44 | imm = (inst >> 20) & 0x7FF; 45 | imm = (~imm & 0x7FF) + 1; 46 | #if __riscv_xlen == 64 47 | return imm >> 3; // RV64: 以 8 字节为单位 48 | #else 49 | return imm >> 2; // RV32: 以 4 字节为单位 50 | #endif 51 | } 52 | 53 | return -1; 54 | } 55 | 56 | /* get framesize from c ins */ 57 | static int riscv_backtrace_framesize_get(unsigned short inst) 58 | { 59 | unsigned int imm = 0; 60 | /* addi sp, sp, -im 61 | * 1141:addi sp,sp,-16 62 | * from spec c.addi FROM https://riscv.github.io/riscv-isa-manual/snapshot/unprivileged/#_integer_register_immediate_instructions 63 | * bit[13-15] = 000 64 | * bit[1:0] = 01 65 | * imm[5] = bit[12] 默认负数 66 | * imm[4:0] = bit[6:2] 67 | * bit[11:7] = 00010 68 | * default:0x1101: 000 1 00010 00000 01 69 | * */ 70 | if ((inst & 0xFF83) == 0x1101) { 71 | imm = (inst >> 2) & 0x1F; 72 | imm = (~imm & 0x1F) + 1; 73 | #if __riscv_xlen == 64 74 | return imm >> 3; // RV64: 以 8 字节为单位 75 | #else 76 | return imm >> 2; // RV32: 以 4 字节为单位 77 | #endif 78 | } 79 | 80 | /* c.addi16sp sp, nzuimm6<<4 81 | * 7101:addi sp,sp,-512 82 | * 7119:addi sp,sp,-128 83 | * from spec c.addi16sp FROM https://riscv.github.io/riscv-isa-manual/snapshot/unprivileged/#_integer_constant_generation_instructions 84 | * bit[12] = imm[9] 默认负数 85 | * bit[11:7] = 00010 //x2 (ra) 86 | * bit[6] = imm[4] 87 | * bit[5] = imm[6] 88 | * bit[4] = imm[8] 89 | * bit[3] = imm[7] 90 | * bit[2] = imm[5] 91 | * bit[15:13] = 011 92 | * default: 0x7101: 011 1 00010 00000 01 93 | */ 94 | if ((inst & 0xFF83) == 0x7101) { 95 | imm = (inst >> 3) & 0x3; 96 | imm = (imm << 1) | ((inst >> 5) & 0x1); 97 | imm = (imm << 1) | ((inst >> 2) & 0x1); 98 | imm = (imm << 1) | ((inst >> 6) & 0x1); 99 | imm = ((~imm & 0x1f) + 1) << 4; 100 | #if __riscv_xlen == 64 101 | return imm >> 3; // RV64: 以 8 字节为单位 102 | #else 103 | return imm >> 2; // RV32: 以 4 字节为单位 104 | #endif 105 | } 106 | 107 | return -1; 108 | } 109 | 110 | static int riscv_backtrace_ra_offset_get1(unsigned int inst) 111 | { 112 | unsigned int imm = 0; 113 | /* 114 | * example: 115 | * 2e112623:sw ra,748(sp) 116 | *from spec sw FROM https://riscv.github.io/riscv-isa-manual/snapshot/unprivileged/#ldst 117 | * BIT[31:25] = imm[11:5] 118 | * BIT[11:7] = imm[4:0] 119 | * BIT[6:0] - 0100011 120 | * BIT[14:12] = 010 121 | * 122 | * default: 0x1122023: 0000000 00001 00010 010 00000 0100011 123 | */ 124 | if ((inst & 0x81FFF07F) == 0x112023) { 125 | imm = (inst >> 7) & 0x1F; 126 | imm |= ((inst >> 25) & 0x7F) << 5; 127 | /* The unit is size_t, So we don't have to move 3 bits to the left */ 128 | #if __riscv_xlen == 64 129 | return imm >> 3; // RV64: 以 8 字节为单位 130 | #else 131 | return imm >> 2; // RV32: 以 4 字节为单位 132 | #endif 133 | } 134 | 135 | return -1; 136 | } 137 | 138 | /* get ra position in the stack */ 139 | static int riscv_backtrace_ra_offset_get(unsigned short inst) 140 | { 141 | unsigned int imm = 0; 142 | /* sw ra, imm(sp) 143 | * example: 144 | * c606: sw ra,12(sp) 145 | * ce06: sw ra,28(sp) 146 | * c206: sw ra,4(sp) 147 | * c006: sw ra,0(sp) 148 | * from spec c.swsp FROM https://riscv.github.io/riscv-isa-manual/snapshot/unprivileged/#_stack_pointer_based_loads_and_stores 149 | * bit[15:13] = 110 150 | * bit[12:9] = imm[5:2] 151 | * bit[8:7] = imm[7:6] 152 | * bit[6:2] = 00001 153 | * bit[1:0] = 01 154 | * default: 0xc006 : 110 000000 00001 10 155 | * 156 | * */ 157 | #if __riscv_xlen == 64 158 | if ((inst & 0xE07F) == 0xE006) { 159 | #else 160 | if ((inst & 0xE07F) == 0xC006) { 161 | #endif 162 | imm = (inst >> 9) & 0x0F; //imm[5:2] 已经偏移最低两位,放大了4倍 163 | imm = imm | (((inst >> 7) & 0x3)<<4); //imm[7:6] 164 | /* The unit is size_t, So we don't have to move 3 bits to the left */ 165 | #if __riscv_xlen == 64 166 | return imm >> 1; // RV64: 以 8 字节为单位 167 | #else 168 | return imm; // RV32: 以 4 字节为单位 169 | #endif 170 | } 171 | 172 | return -1; 173 | } 174 | static char *k_int64tostr(int64_t num, char *str) 175 | { 176 | char index[] = "0123456789ABCDEF"; 177 | unsigned int usnum = (unsigned int)num; 178 | 179 | str[7] = index[usnum % 16]; 180 | usnum /= 16; 181 | str[6] = index[usnum % 16]; 182 | usnum /= 16; 183 | str[5] = index[usnum % 16]; 184 | usnum /= 16; 185 | str[4] = index[usnum % 16]; 186 | usnum /= 16; 187 | str[3] = index[usnum % 16]; 188 | usnum /= 16; 189 | str[2] = index[usnum % 16]; 190 | usnum /= 16; 191 | str[1] = index[usnum % 16]; 192 | usnum /= 16; 193 | str[0] = index[usnum % 16]; 194 | usnum /= 16; 195 | 196 | return str; 197 | } 198 | 199 | /* get the offset between the jump instruction and the return address */ 200 | static int backtraceFindLROffset(char *LR) 201 | { 202 | int offset = 0; 203 | char *LR_indeed; 204 | unsigned int ins32; 205 | 206 | LR_indeed = BT_PC2ADDR(LR); 207 | 208 | /* Usually jump using the JAL instruction */ 209 | //ins32 = *(unsigned int *)(LR_indeed - 4); 210 | rv_memcpy(&ins32, (LR_indeed - 4), sizeof(ins32)); 211 | if ((ins32 & 0x3) == 0x3) { 212 | offset = 4; 213 | } else { 214 | offset = 2; 215 | } 216 | 217 | return offset; 218 | } 219 | 220 | static int riscv_backtraceFromStack(uint32_t **pSP, char **pPC) 221 | { 222 | char *CodeAddr = NULL; 223 | #if __riscv_xlen == 64 224 | uint64_t *SP = *pSP; 225 | #else 226 | uint32_t *SP = *pSP; 227 | #endif 228 | char *PC = *pPC; 229 | char *LR; 230 | int i; 231 | int framesize; 232 | int offset = 0; 233 | unsigned int ins32; 234 | unsigned short ins16; 235 | 236 | /* 1. scan code, find lr pushed */ 237 | for (i = 0; i < BT_FUNC_LIMIT;) { 238 | /* FIXME: not accurate from bottom to up. how to judge 2 or 4byte inst */ 239 | //CodeAddr = (char *)(((long)PC & (~0x3)) - i); 240 | CodeAddr = (char *)(PC - i); 241 | //ins32 = *(unsigned int *)(CodeAddr); 242 | rv_memcpy(&ins32, CodeAddr, sizeof(ins32)); 243 | //DEBUG_RV_BACKTRACE("\n=========CodeAddr:%p==ins32:%x===",CodeAddr,ins32); 244 | if ((ins32 & 0x3) == 0x3) { 245 | ins16 = *(unsigned short *)(CodeAddr - 2); 246 | if ((ins16 & 0x3) != 0x3) { 247 | i += 4; 248 | framesize = riscv_backtrace_framesize_get1(ins32); 249 | if (framesize >= 0) { 250 | // BACKTRACE_PRINTF("\n=========CodeAddr:%p==ins32:%x=framesize:%d==",CodeAddr,ins32,framesize); 251 | CodeAddr += 4; 252 | break; 253 | } 254 | continue; 255 | } 256 | } 257 | i += 2; 258 | ins16 = (ins32 >> 16) & 0xffff; 259 | framesize = riscv_backtrace_framesize_get(ins16); 260 | if (framesize >= 0) { 261 | //DEBUG_RV_BACKTRACE("\n=========CodeAddr:%p==ins16:%x==framesize:%d=",CodeAddr,ins16,framesize); 262 | CodeAddr += 2; 263 | break; 264 | } 265 | } 266 | 267 | if (i == BT_FUNC_LIMIT) { 268 | /* error branch */ 269 | #ifdef BACKTRACE_PRINTF 270 | BACKTRACE_PRINTF("Backtrace fail!\r\n"); 271 | #endif 272 | return -1; 273 | } 274 | 275 | /* 2. scan code, find ins: sd ra,24(sp) or sd ra,552(sp) */ 276 | for (i = 0; CodeAddr + i < PC;) { 277 | //ins32 = *(unsigned int *)(CodeAddr + i); 278 | rv_memcpy(&ins32, (CodeAddr+i), sizeof(ins32)); 279 | //DEBUG_RV_BACKTRACE("\n==2=======CodeAddr:%p==ins32:%x===",CodeAddr,ins32); 280 | if ((ins32 & 0x3) == 0x3) { 281 | i += 4; 282 | offset = riscv_backtrace_ra_offset_get1(ins32); 283 | if (offset >= 0) { 284 | //DEBUG_RV_BACKTRACE("\n=========CodeAddr:%p==ins32:%x==offset:%d=",CodeAddr,ins32,offset); 285 | break; 286 | } 287 | } else { 288 | i += 2; 289 | ins16 = ins32 & 0xffff; 290 | offset = riscv_backtrace_ra_offset_get(ins16); 291 | if (offset >= 0) { 292 | //DEBUG_RV_BACKTRACE("\n=========CodeAddr:%p==ins16:%x==offset:%d=",CodeAddr,ins16,offset); 293 | break; 294 | } 295 | } 296 | } 297 | 298 | /* 3. output */ 299 | LR = (char *) * (SP + offset); 300 | 301 | if (BT_CHK_PC_AVAIL(LR) == 0) { 302 | #ifdef BACKTRACE_PRINTF 303 | BACKTRACE_PRINTF("Backtrace fail!\r\n"); 304 | #endif 305 | return -1; 306 | } 307 | *pSP = SP + framesize; 308 | offset = backtraceFindLROffset(LR); 309 | 310 | rvstack_frame[lvl] = (unsigned int)(LR - offset); 311 | 312 | BACKTRACE_PRINTF("[%d]Stack interval :[0x%016lx - 0x%016lx] ra 0x%016lx pc 0x%016lx\n", lvl, SP, SP + framesize, LR, LR - offset); 313 | *pPC = LR - offset; 314 | 315 | return offset == 0 ? 1 : 0; 316 | } 317 | 318 | static int backtraceFromStack(uint32_t **pSP, char **pPC) 319 | { 320 | if (BT_CHK_PC_AVAIL(*pPC) == 0) { 321 | return -1; 322 | } 323 | 324 | return riscv_backtraceFromStack(pSP, pPC); 325 | } 326 | 327 | /* get the return address of the current function */ 328 | __attribute__((always_inline)) static inline void *backtrace_get_sp(void) 329 | { 330 | void *sp; 331 | __asm__ volatile("mv %0, sp\n" : "=r"(sp)); 332 | return sp; 333 | } 334 | 335 | /* get the return address of the current function */ 336 | __attribute__((always_inline)) static inline void *backtrace_get_pc(void) 337 | { 338 | void *pc; 339 | __asm__ volatile("auipc %0, 0\n" : "=r"(pc)); 340 | return pc; 341 | } 342 | 343 | /* printf call stack 344 | return levels of call stack */ 345 | int rvbacktrace_fomit(void) 346 | { 347 | char *PC; 348 | uint32_t *SP; 349 | int ret; 350 | 351 | SP = backtrace_get_sp(); 352 | PC = backtrace_get_pc(); 353 | 354 | BACKTRACE_PRINTF("\r\n---- RV_Backtrace Call Frame Start: -SP_size:%x---\r\n",sizeof(SP)); 355 | BACKTRACE_PRINTF("###Please consider the value of ra as accurate and the value of sp as only for reference###\n"); 356 | #ifdef RT_VER_NUM 357 | BACKTRACE_PRINTF("------------------------------Thread: %s backtrace------------------------------\r\n", ((rt_thread_t)rt_thread_self())->parent.name); 358 | #endif 359 | BACKTRACE_PRINTF("----SP:0x%x----PC:0x%x----\r\n",SP,PC); 360 | BACKTRACE_PRINTF("----TEXT start :0x%x----end:0x%x----\r\n",&BACKTRACE_TEXT_START,&BACKTRACE_TEXT_END); 361 | for (lvl = 0; lvl < BT_LVL_LIMIT; lvl++) { 362 | ret = backtraceFromStack(&SP, &PC); 363 | if (ret != 0) { 364 | rvstack_frame_len = lvl; 365 | break; 366 | } 367 | } 368 | rvbacktrace_addr2line((uint32_t *)&rvstack_frame[0]); 369 | BACKTRACE_PRINTF("---- RV_Backtrace Call Frame End:----\r\n"); 370 | BACKTRACE_PRINTF("\r\n"); 371 | return lvl; 372 | } 373 | void rvbacktrace_info(uint32_t *uSP, uint32_t *uPC) 374 | { 375 | char *PC; 376 | uint32_t *SP; 377 | SP = uSP; 378 | PC = (char *)uPC; 379 | int ret; //先保存PC的值,方便addr2line使用 380 | rvstack_frame[0] = (unsigned int)(PC); 381 | for (lvl = 1; lvl < BT_LVL_LIMIT; lvl++) { 382 | ret = backtraceFromStack(&SP, &PC); 383 | if (ret != 0) { 384 | rvstack_frame_len = lvl; 385 | break; 386 | } 387 | } 388 | rvbacktrace_addr2line((uint32_t *)&rvstack_frame[0]); // addr2line function 389 | } 390 | #ifdef RT_USING_FINSH 391 | void rv_backtrace_fomit_func(void) 392 | { 393 | rvbacktrace_fomit(); 394 | } 395 | MSH_CMD_EXPORT_ALIAS(rv_backtrace_fomit_func, rv_backtrace_fomit, backtrace fomit); 396 | 397 | void rv_backtrace_hardfault_test(void) 398 | { 399 | uint32_t *PC; 400 | uint32_t *SP; 401 | int ret; 402 | 403 | SP = backtrace_get_sp(); 404 | PC = backtrace_get_pc(); 405 | 406 | rvbacktrace_info(SP, PC); 407 | } 408 | MSH_CMD_EXPORT_ALIAS(rv_backtrace_hardfault_test, rv_backtrace_int_test, backtrace fomit); 409 | #endif 410 | -------------------------------------------------------------------------------- /tools/example/backtrace.txt: -------------------------------------------------------------------------------- 1 | \ | / 2 | - RT - Thread Operating System 3 | / | \ 5.0.2 build Aug 28 2024 16:40:56 4 | 2006 - 2022 Copyright by RT-Thread team 5 | ---- RV_Backtrace Call Frame Start: ---- 6 | ###Please consider the value of ra as accurate and the value of sp as only for reference### 7 | [0]Stack interval :[0x000000000108ea30 - 0x000000000108ea40] ra 0x000000008000ff28 pc 0x000000008000ff24 8 | [1]Stack interval :[0x000000000108ea40 - 0x000000000108ea50] ra 0x000000008000fe7c pc 0x000000008000fe78 9 | [2]Stack interval :[0x000000000108ea50 - 0x000000000108ea60] ra 0x0000000080010002 pc 0x000000008000fffe 10 | [3]Stack interval :[0x000000000108ea60 - 0x000000000108ea70] ra 0x000000008000688e pc 0x000000008000688a 11 | [4]Stack interval :[0x000000000108ea70 - 0x00000000deadbeef] ra 0x0000000001086a7c pc 0x0000000001086a78 12 | ---- RV_Backtrace Call Frame End:---- 13 | msh > 14 | -------------------------------------------------------------------------------- /tools/tracefunction.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | current_dir = os.path.dirname(os.path.abspath(__file__)) 5 | path_txt_path = os.path.join(current_dir, 'obj', 'path.txt') 6 | 7 | if os.path.exists(path_txt_path): 8 | with open(path_txt_path, 'r', encoding='utf-8') as path_file: 9 | for line in path_file: 10 | if line.strip().startswith('info_path ='): 11 | info_path_raw = line.split('=')[-1].strip().strip('"').strip("'") 12 | # 将info_path转换为绝对路径(如果它不是的话) 13 | info_path_abs = os.path.abspath(info_path_raw) 14 | # 去除文件名部分,只保留目录路径 15 | info_dir_path = os.path.dirname(info_path_abs) 16 | directory = info_dir_path 17 | 18 | # 确保路径存在且为目录 19 | if not os.path.exists(directory) or not os.path.isdir(directory): 20 | print("指定的路径不存在或不是一个目录。") 21 | exit() 22 | 23 | # 栈帧计数器 24 | frame_number = 0 25 | 26 | # 输出文件的完整路径 27 | output_file_path = os.path.join(current_dir, 'obj', 'function_pc.txt') 28 | # output_dir = 'obj' 29 | # if not os.path.exists(output_dir): 30 | # os.makedirs(output_dir) # 如果文件夹不存在,则创建它 31 | # output_file_path = os.path.join(output_dir, 'function_pc.txt') 32 | 33 | # 尝试打开(或创建)输出文件 34 | with open(output_file_path, 'w', encoding='utf-8') as output_file: 35 | # 遍历目录下的所有.txt文件 36 | for filename in os.listdir(directory): 37 | if filename.endswith('.txt'): 38 | file_path = os.path.join(directory, filename) 39 | 40 | try: 41 | with open(file_path, 'r', encoding='utf-8') as file: 42 | for line in file: 43 | # 检查行中是否包含"pc"(不区分大小写) 44 | if 'pc' in line.lower(): 45 | # 在"pc"之后搜索以"0x"开头的十六进制数据 46 | hex_match = re.search(r'pc\s*0x[0-9a-fA-F]+', line) 47 | if hex_match: 48 | # 提取十六进制数据部分 49 | hex_data = hex_match.group(0).split(' ', 1)[-1].strip() 50 | # 去除前导空格和非十六进制字符(这里其实已经由上一个正则保证了) 51 | # 但为了清晰,保留这一行,实际上可能不需要再次搜索 52 | # hex_data = re.search(r'\b0x[0-9a-fA-F]+\b', hex_data).group(0) 53 | # 打印栈帧级别和栈帧地址(可选,用于控制台输出) 54 | print(f"[RV] 第{frame_number + 1}级栈帧,栈帧地址: {hex_data}") 55 | # 将栈帧地址写入输出文件 56 | output_file.write(hex_data + '\n') 57 | # 更新栈帧计数器 58 | frame_number += 1 59 | 60 | except Exception as e: 61 | print(f"无法读取文件 {file_path}: {e}") 62 | -------------------------------------------------------------------------------- /tools/tracehtml.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | 4 | def generate_html_table(input_file_relative, output_filename): 5 | # 获取当前脚本所在的目录 6 | script_dir = os.path.dirname(os.path.abspath(__file__)) 7 | 8 | # 构建完整的input_file路径 9 | input_file = os.path.join(script_dir, 'obj', input_file_relative) 10 | 11 | # 构建完整的输出文件路径,包括html文件夹 12 | output_dir = os.path.join(script_dir, 'html') 13 | if not os.path.exists(output_dir): 14 | os.makedirs(output_dir) # 如果文件夹不存在,则创建它 15 | output_file = os.path.join(output_dir, output_filename) 16 | 17 | with open(input_file, 'r') as f: 18 | lines = f.readlines() 19 | 20 | table_data = [] 21 | for line in lines: 22 | match = re.match(r'\s*([0-9a-fA-F]+):\s+([0-9a-fA-F]+)\s+(.*?)<([^>]+)>', line) 23 | if match: 24 | address = match.group(1).strip() 25 | instruction = match.group(2).strip() 26 | asm = match.group(3).strip() 27 | function_name = match.group(4).strip() 28 | table_data.append((address, instruction, asm, function_name)) 29 | 30 | # 构建函数调用栈字符串 31 | call_stack = " <- ".join(data[3] for data in table_data) 32 | 33 | html = """ 34 | 35 | 36 | 37 | RVBacktrace 38 | 43 | 44 | 45 |

RVBacktrace/栈回溯信息

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | """ 54 | 55 | for data in table_data: 56 | html += f"" 57 | 58 | html += """ 59 |
函数地址指令编码汇编函数名称
{data[0]}{data[1]}{data[2]}{data[3]}
60 |

函数调用栈

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
调用栈
{call_stack}
69 | 70 | 71 | """.format(call_stack=call_stack) 72 | 73 | with open(output_file, 'w') as f: 74 | f.write(html) 75 | 76 | # 调用函数,这里只传递文件名(相对于obj文件夹),而不是完整路径 77 | generate_html_table("info.txt", "rvbacktrace.html") -------------------------------------------------------------------------------- /tools/traceinfo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | def extract_hex_from_file(file_path): 5 | """从指定文件中提取所有以0x开头的十六进制数据(去除'0x'前缀)""" 6 | hex_values = [] 7 | with open(file_path, 'r', encoding='utf-8') as file: 8 | for line in file: 9 | if line.strip().startswith('0x'): 10 | hex_values.append(line.strip()[2:]) # 去除'0x'并保留整个十六进制数 11 | return hex_values 12 | 13 | def read_asm_path_from_file(path_txt_file): 14 | """从path.txt文件中读取asm_path字段的值""" 15 | asm_path = None 16 | with open(path_txt_file, 'r', encoding='utf-8') as file: 17 | for line in file: 18 | if line.strip().startswith('asm_path = '): 19 | # 假设路径是被单引号或双引号包围的 20 | match = re.match(r'asm_path = ([\'"])(.*?)\1', line.strip()) 21 | if match: 22 | asm_path = match.group(2) 23 | break 24 | return asm_path 25 | 26 | def find_matches_in_asm(asm_path, hex_values, output_dir='obj'): 27 | """在ASM文件中查找与给定十六进制数值列表中的每个值相等的行, 28 | 并根据冒号后数据的十六进制值是否小于0x10000来决定输出当前行还是下一行, 29 | 最后将信息输出到指定目录下的info.txt文件中。 30 | """ 31 | # 确保输出目录存在 32 | # if not os.path.exists(output_dir): 33 | # os.makedirs(output_dir) 34 | script_path = os.path.abspath(__file__) 35 | current_dir = os.path.dirname(script_path) 36 | # 构建obj文件夹的路径 37 | obj_dir = os.path.join(current_dir, 'obj') 38 | # 构建完整的输出文件路径 39 | output_file_path = os.path.join(obj_dir, 'info.txt') 40 | # 尝试以写入模式打开info.txt文件 41 | with open(output_file_path, 'w', encoding='utf-8') as info_file: 42 | with open(asm_path, 'r', encoding='utf-8') as file: 43 | asm_lines = file.readlines() # 一次性读取所有行到列表中 44 | for hex_value in hex_values: 45 | hex_int = int(hex_value, 16) 46 | for i, line in enumerate(asm_lines): 47 | match = re.match(r'([0-9a-fA-F]+):\s*([0-9a-fA-F]+).*', line.strip()) 48 | if match and int(match.group(1), 16) == hex_int: 49 | data_hex = match.group(2) 50 | data_int = int(data_hex, 16) 51 | 52 | if data_int < 0x10000: 53 | if i + 1 < len(asm_lines): 54 | next_line = asm_lines[i + 1] 55 | info_file.write(next_line) 56 | else: 57 | info_file.write(line) 58 | 59 | def main(): 60 | # 获取脚本所在工作目录 61 | script_path = os.path.abspath(__file__) 62 | current_dir = os.path.dirname(script_path) 63 | # 构建obj文件夹的路径 64 | obj_dir = os.path.join(current_dir, 'obj') 65 | # 构建path.txt的完整路径 66 | path_txt_file = os.path.join(obj_dir, 'path.txt') 67 | # 从path.txt文件中读取asm_path 68 | asm_path = read_asm_path_from_file(path_txt_file) 69 | if not asm_path: 70 | print("无法在path.txt文件中找到asm_path字段。") 71 | return 72 | 73 | # 假设function_pc.txt也位于obj文件夹中 74 | function_pc_path = os.path.join(obj_dir, 'function_pc.txt') 75 | # 从function_pc.txt中提取所有十六进制数 76 | hex_values = extract_hex_from_file(function_pc_path) 77 | 78 | # 检查是否成功提取到数据 79 | if not hex_values: 80 | print("function_pc.txt中没有找到以0x开头的有效十六进制数。") 81 | else: 82 | # 在rtthread.asm中查找匹配的行并按function_pc.txt中的顺序输出 83 | find_matches_in_asm(asm_path, hex_values) 84 | 85 | if __name__ == "__main__": 86 | main() -------------------------------------------------------------------------------- /tools/tracepath.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # 获取当前脚本文件所在路径 4 | current_script_path = os.path.abspath(__file__) 5 | current_script_dir = os.path.dirname(current_script_path) 6 | 7 | # 设定obj文件夹的路径,基于当前脚本所在目录 8 | obj_folder_path = os.path.join(current_script_dir, 'obj') 9 | 10 | # path.txt文件的完整路径 11 | path_txt_file = os.path.join(obj_folder_path, 'path.txt') 12 | 13 | # 检查obj文件夹是否存在,如果不存在则创建 14 | if not os.path.exists(obj_folder_path): 15 | os.makedirs(obj_folder_path) 16 | 17 | # 检查path.txt文件是否存在以及是否包含asm_path和info_path字段 18 | def check_and_get_existing_paths(file_path): 19 | existing_paths = {} 20 | if os.path.exists(file_path): 21 | with open(file_path, 'r') as file: 22 | for line in file: 23 | stripped_line = line.strip() 24 | if stripped_line.startswith(('asm_path = ', 'info_path = ')): 25 | key, value = stripped_line.split(' = ', 1) 26 | existing_paths[key] = value.strip("'") 27 | return existing_paths 28 | 29 | # 获取已存在的路径 30 | existing_paths = check_and_get_existing_paths(path_txt_file) 31 | 32 | # 处理asm_path 33 | asm_path_found = 'asm_path' in existing_paths 34 | if not asm_path_found: 35 | directory_path = input("[RV] 请输入包含汇编文件的目录路径: ") 36 | if not os.path.isdir(directory_path): 37 | print("[RV] 错误:输入的不是一个有效的目录。") 38 | exit() 39 | 40 | assembly_files = [] 41 | for root, dirs, files in os.walk(directory_path): 42 | for file in files: 43 | if file.endswith(('.s', '.S', '.ASM', '.asm')): 44 | assembly_files.append(os.path.abspath(os.path.join(root, file))) 45 | 46 | if assembly_files: 47 | with open(path_txt_file, 'a') as file: # 使用追加模式 48 | if os.path.getsize(path_txt_file) > 0: # 如果文件不为空,则添加换行符 49 | file.write('\n') 50 | for file_path in assembly_files: 51 | # 保存每个汇编文件的完整路径 52 | file.write(f"asm_path = '{file_path}'\n") 53 | print(f"[RV] 汇编文件的完整路径已保存到: {path_txt_file}") 54 | else: 55 | print("[RV] 在指定目录下未找到汇编文件。") 56 | else: 57 | print("[RV] 路径文件path.txt已存在且包含asm_path字段,无需重新输入目录。") 58 | 59 | # 处理info_path(逻辑与asm_path类似,但这里保留列出所有文件的逻辑) 60 | info_path_found = 'info_path' in existing_paths 61 | if not info_path_found: 62 | directory_path = input("[RV] 请输入包含串口打印文件的目录路径: ") 63 | if not os.path.isdir(directory_path): 64 | print("错误:输入的不是一个有效的目录。") 65 | exit() 66 | 67 | info_files = [] 68 | for root, dirs, files in os.walk(directory_path): 69 | for file in files: 70 | if file.endswith(('.txt', '.TXT')): 71 | info_files.append(os.path.abspath(os.path.join(root, file))) 72 | 73 | if info_files: 74 | with open(path_txt_file, 'a') as file: # 使用追加模式 75 | if os.path.getsize(path_txt_file) > 0 and not file.tell() == 0: # 如果文件不为空且不是在文件开头 76 | file.write('\n') 77 | for file_path in info_files: 78 | file.write(f"info_path = '{file_path}'\n") 79 | print(f"[RV] 串口打印文件的绝对路径已保存到: {path_txt_file}") 80 | else: 81 | print("在指定目录下未找到串口打印文件。") 82 | else: 83 | print("[RV] 路径文件path.txt已存在且包含info_path字段,无需重新输入目录。") --------------------------------------------------------------------------------