├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets ├── excevt.png └── pcsampl.png └── src └── bin ├── excevt.rs ├── itm-decode.rs ├── pcsampl.rs └── port-demux.rs /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | /target 4 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "itm-tools" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | clap = "2.32.0" 9 | exitfailure = "0.5.1" 10 | failure = "0.1.5" 11 | itm = { git = "https://github.com/rust-embedded/itm" } 12 | rustc-demangle = "0.1.13" 13 | xmas-elf = "0.6.2" 14 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jorge Aparicio 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository is no longer maintained! 2 | The library on which these tools are based has been deprecated in favor of [rtic-scope/itm]. 3 | You may want to check the tools developed by the [rtic-scope] team. 4 | 5 | [rtic-scope/itm]: https://github.com/rtic-scope/itm 6 | [rtic-scope]: https://github.com/rtic-scope 7 | 8 | --- 9 | 10 | # `itm-tools` 11 | 12 | > Tools for analyzing ITM traces. 13 | 14 |

15 | Tracing interrupt handling 19 |
20 | Tracing interrupt handling 21 |

22 | 23 | This set of tools currently supports: 24 | 25 | - [Exception tracing](#exception-tracing), via `excevt` 26 | - [PC sampling](#pc-sampling), via `pcsampl`, and 27 | - [Port demuxing](#port-demuxing), via `port-demux` 28 | 29 | **NOTE:** These tools have been designed to deal with ITM traces that contain 30 | only few different, but related, packet types. If your ITM traces contain 31 | timestamps, PC sampling, instrumentation, exception trace and other kind of 32 | packets, all intermixed, then these tools won't work for you. In practice, 33 | though, it's likely that you'll only trace one aspect at a time due to the 34 | bandwidth limit of the ITM output. 35 | 36 | ## Exception tracing 37 | 38 | The ITM can generate an exception trace packet any time the processor enters, 39 | leaves or returns from an interrupt. Timestamp packets can be attached to these 40 | packets. This information can be used to trace interrupt prioritization and 41 | measure the execution time of interrupt handlers. `excevt` simplifies the 42 | analysis and visualization of this information. 43 | 44 | To configure the ITM for exception tracing you can add the following commands 45 | to your GDB script. Alternatively, you can configure the ITM peripheral from 46 | the application. 47 | 48 | ``` console 49 | $ tail openocd.gdb 50 | # NOTE: pick ONE of these (see cortex-m-quickstart for more details) 51 | # monitor tpiu config internal itm.bin uart off 8000000 52 | monitor tpiu config external uart off 8000000 2000000 53 | 54 | # set EXCEVTENA; clear PCSAMPLENA 55 | monitor mmw 0xE0001000 65536 4096 56 | 57 | # on the STM32F1 the timestamp counter will not stop when the processor is 58 | # halted. This results in timestamp packets being emitted when the processor is 59 | # halted. So we disable timestamping when halting the processor from the 60 | # debugger (e.g. # bkpt) 61 | define hook-stop 62 | echo clear TSENA\n 63 | monitor mmw 0xE0000E80 0 2 64 | end 65 | ``` 66 | 67 | **NOTE:** In practice, you should not mix debugging and tracing as the debugger 68 | will interfere with timestamps and other event counters -- this is ARM's 69 | recommendation. We mix them here because that makes the tools easier to try out 70 | and these are just examples. 71 | 72 | Here's an example [RTFM] application that features a few interrupt handlers that 73 | preempt each other. 74 | 75 | [RTFM]: https://crates.io/crates/cortex-m-rtfm 76 | 77 | ``` rust 78 | #[rtfm::app(device = stm32f103xx)] 79 | const APP: () = { 80 | #[init] 81 | fn init() { 82 | rtfm::pend(Interrupt::EXTI0); 83 | 84 | // set TSENA: enable local timestamps 85 | unsafe { 86 | core.ITM.tcr.modify(|r| r | (1 << 1)); 87 | } 88 | } 89 | 90 | // taken after `init` returns 91 | #[interrupt(priority = 1)] 92 | fn EXTI0() { // IRQ(6) 93 | rtfm::pend(Interrupt::EXTI2); 94 | 95 | // wait a bit so all ITM packets are flushed 96 | asm::delay(256); 97 | 98 | asm::bkpt(); // stop tracing 99 | } 100 | 101 | #[interrupt(priority = 2)] 102 | fn EXTI1() { // IRQ(7) 103 | asm::delay(256); 104 | } 105 | 106 | #[interrupt(priority = 3)] 107 | fn EXTI2() { // IRQ(8) 108 | // NOTE: EXTI1 has lower priority 109 | rtfm::pend(Interrupt::EXTI1); 110 | 111 | asm::delay(512); 112 | } 113 | }; 114 | ``` 115 | 116 | Collecting ITM traces from this application, 117 | 118 | 119 | ``` console 120 | $ # collect ITM data 121 | $ cat /dev/ttyUSB0 > itm.bin 122 | ``` 123 | 124 | ``` console 125 | $ # on another terminal: run program to `asm::bkpt` 126 | $ cargo run --release 127 | ``` 128 | 129 | Produces these packets: 130 | 131 | ``` console 132 | $ itm-decode itm.bin 133 | ExceptionTrace { function: Enter, number: 22 } 134 | LocalTimestamp { delta: 30, tc: 0, len: 2 } 135 | ExceptionTrace { function: Enter, number: 24 } 136 | LocalTimestamp { delta: 20, tc: 0, len: 2 } 137 | ExceptionTrace { function: Exit, number: 24 } 138 | LocalTimestamp { delta: 528, tc: 0, len: 3 } 139 | ExceptionTrace { function: Enter, number: 23 } 140 | LocalTimestamp { delta: 3, tc: 0, len: 1 } 141 | ExceptionTrace { function: Exit, number: 23 } 142 | LocalTimestamp { delta: 268, tc: 0, len: 3 } 143 | ExceptionTrace { function: Return, number: 22 } 144 | LocalTimestamp { delta: 7, tc: 0, len: 2 } 145 | ``` 146 | 147 | Which can be better visualized using the `excevt` tool: 148 | 149 | ``` console 150 | $ excevt -t itm.bin 151 | !000000000 → IRQ(6) 152 | =000000020 → IRQ(8) 153 | =000000548 ← IRQ(8) 154 | =000000551 → IRQ(7) 155 | =000000819 ← IRQ(7) 156 | =000000826 ↓ IRQ(6) 157 | ``` 158 | 159 | The left column shows the timestamp of the events. `=` means a precise 160 | timestamp, `<` means that the event occurred before the reported timestamp and 161 | `!` means that the counter was reset due to packet loss. 162 | 163 | The arrows on the second column indicate whether the processor entered the 164 | interrupt (`→`), left the interrupt (`←`) or returned to the interrupt handler 165 | (`↓`). 166 | 167 | The last column indicates the interrupt, or exception, associated to the event. 168 | `IRQ(n)` means a device specific interrupt. For Cortex-M exceptions you'll see 169 | the standard name, for example `SysTick`. Finally, you may also see the word 170 | `Thread`, which indicates *thread mode* (that is not servicing any interrupt or 171 | exception). 172 | 173 | `excevt` also works when timestamps are disabled. For example, if you comment 174 | out the setting `TSENA` in the above example and re-run the program, you'll get 175 | these outputs from `itm-decode` and `excevt`: 176 | 177 | ``` console 178 | $ itm-decode itm.bin 179 | ExceptionTrace { function: Enter, number: 22 } 180 | ExceptionTrace { function: Enter, number: 24 } 181 | ExceptionTrace { function: Exit, number: 24 } 182 | ExceptionTrace { function: Enter, number: 23 } 183 | ExceptionTrace { function: Exit, number: 23 } 184 | ExceptionTrace { function: Return, number: 22 } 185 | ``` 186 | 187 | ``` console 188 | $ excevt itm.bin 189 | ????????? → IRQ(6) 190 | ????????? → IRQ(8) 191 | ????????? ← IRQ(8) 192 | ????????? → IRQ(7) 193 | ????????? ← IRQ(7) 194 | ????????? ↓ IRQ(6) 195 | ``` 196 | 197 | ## PC sampling 198 | 199 |

200 | Profiling firmware 204 |
205 | Profiling firmware 206 |

207 | 208 | 209 | The ITM can also be configured to output periodic packets that contain snapshots 210 | of the program counter. These can be used to answer the question: where is my 211 | program spending most of its time? `pcsampl` can process the data and answer 212 | this question. 213 | 214 | To configure the ITM for periodic PC sampling you can add the following commands 215 | to your GDB script. 216 | 217 | ``` console 218 | $ tail openocd.gdb 219 | # NOTE: pick ONE of these (see cortex-m-quickstart for more details) 220 | # monitor tpiu config internal itm.bin uart off 8000000 221 | monitor tpiu config external uart off 8000000 2000000 222 | 223 | echo clear EXCEVTENA; set PCSAMPLENA\n 224 | monitor mmw 0xE0001000 4096 65536 225 | echo enable CYCCNT; set POSTINIT / POSTRESET to 3\n 226 | monitor mmw 0xE0001000 103 510 227 | ``` 228 | 229 | Here's an example RTFM application that runs two periodic tasks and sleeps when 230 | none of the tasks is active. 231 | 232 | ``` rust 233 | #[rtfm::app(device = stm32f103xx)] 234 | const APP: () = { 235 | #[init(spawn = [foo, bar])] 236 | fn init() { 237 | // bootstrap periodic tasks 238 | spawn.foo().unwrap(); 239 | spawn.bar().unwrap(); 240 | } 241 | 242 | #[task(priority = 1, schedule = [foo])] 243 | #[inline(never)] 244 | fn foo() { 245 | static mut COUNT: u8 = 0; 246 | 247 | // fake work 248 | asm::delay(1024); 249 | 250 | *COUNT += 1; 251 | if *COUNT > 100 { 252 | asm::bkpt(); // stop tracing 253 | } else { 254 | schedule.foo(scheduled + 30_000.cycles()).unwrap(); 255 | } 256 | } 257 | 258 | #[task(priority = 2, schedule = [bar])] 259 | #[inline(never)] 260 | fn bar() { 261 | // fake work 262 | asm::delay(512); 263 | 264 | schedule.bar(scheduled + 20_000.cycles()).unwrap(); 265 | } 266 | 267 | extern "C" { 268 | fn EXTI0(); 269 | fn EXTI1(); 270 | } 271 | }; 272 | ``` 273 | 274 | Collecting the ITM packets produces a few kilobytes of data: 275 | 276 | ``` console 277 | $ itm-decode itm.bin 2>/dev/null | wc 278 | 11714 58591 362321 279 | ``` 280 | 281 | This information can be summarized using the `pcsampl` tool: 282 | 283 | ``` console 284 | $ pcsampl -e target/thumbv7m-none-eabi/release/app itm.bin 2>/dev/null 285 | % FUNCTION 286 | 91.69 *SLEEP* 287 | 3.70 app::foo_o7xa::h9e4953f3ea6a58d8 288 | 2.87 app::bar_t7fm::hf544b1b6f026d266 289 | 0.95 SysTick 290 | 0.52 EXTI1 291 | 0.26 EXTI0 292 | 0.01 main 293 | ----- 294 | 100% 11692 samples 295 | ``` 296 | 297 | The percentage of time spent sleeping is always displayed first. Afterwards, the 298 | percentage of time spent in other functions is reported, in descending order. 299 | 300 | ## Port demuxing 301 | 302 | The ITM lets the software send instrumentation packets. These packets carry a 303 | stimulus port number which the application can use to mux different sources of 304 | information into a single stream of data. Naturally, the receiver must demux 305 | this data back into the original streams. `port-demux` provides such 306 | functionality. 307 | 308 | To enable port muxing of instrumentation packets you can add the following 309 | commands to your GDB script. 310 | 311 | ``` console 312 | $ tail openocd.gdb 313 | # NOTE: pick ONE of these (see cortex-m-quickstart for more details) 314 | # monitor tpiu config internal itm.bin uart off 8000000 315 | monitor tpiu config external uart off 8000000 2000000 316 | 317 | # enable ITM ports 318 | monitor itm port 0 on 319 | monitor itm port 1 on 320 | monitor itm port 2 on 321 | 322 | # use this if you used any of the other GDB script snippets 323 | echo clear EXCEVTENA and PCSAMPLENA\n 324 | monitor mmw 0xE0001000 0 69632 325 | ``` 326 | 327 | Here's a `cortex-m-rt` application that reports data using three different 328 | stimulus ports. 329 | 330 | ``` rust 331 | #[entry] 332 | fn main() -> ! { 333 | let mut p = cortex_m::Peripherals::take().unwrap(); 334 | 335 | iprint!(&mut p.ITM.stim[0], "Hell"); 336 | 337 | // imagine this interrupts the port 0 write 338 | iprint!(&mut p.ITM.stim[1], "The "); 339 | 340 | // imagine this interrupts the port 1 write 341 | iprintln!(&mut p.ITM.stim[2], "The answer is 42"); 342 | 343 | // resume port 1 write 344 | iprintln!( 345 | &mut p.ITM.stim[1], 346 | "quick brown fox jumps over the lazy dog" 347 | ); 348 | 349 | // resume port 0 write 350 | iprintln!(&mut p.ITM.stim[0], "o, world!"); 351 | 352 | asm::bkpt(); 353 | 354 | loop {} 355 | } 356 | ``` 357 | 358 | You can set up `port-demux` to demux the stream of data as it's received. 359 | 360 | ``` console 361 | $ cat /dev/ttyUSB0 | port-demux -f 362 | ``` 363 | 364 | And then watch over the demuxed streams 365 | 366 | ``` console 367 | $ # on another terminal 368 | $ tail -f 0.stim 369 | Hello, world! 370 | ``` 371 | 372 | ``` console 373 | $ # on another terminal 374 | $ tail -f 1.stim 375 | The quick brown fox jumps over the lazy dog 376 | ``` 377 | 378 | ``` console 379 | $ # on another terminal 380 | $ tail -f 2.stim 381 | The answer is 42 382 | ``` 383 | 384 | ## License 385 | 386 | The code in this repository is distributed under the terms of both the MIT 387 | license and the Apache License (Version 2.0). 388 | 389 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 390 | -------------------------------------------------------------------------------- /assets/excevt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japaric/itm-tools/583992a7fd3d1e293f8dca0944c0fe83dc378edd/assets/excevt.png -------------------------------------------------------------------------------- /assets/pcsampl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japaric/itm-tools/583992a7fd3d1e293f8dca0944c0fe83dc378edd/assets/pcsampl.png -------------------------------------------------------------------------------- /src/bin/excevt.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use core::{fmt, u32}; 4 | use std::{ 5 | fs::File, 6 | io::{self, Read, StdoutLock, Write}, 7 | }; 8 | 9 | use clap::{App, Arg}; 10 | use exitfailure::ExitFailure; 11 | use itm::{ 12 | packet::{ExceptionTrace, Function}, 13 | Packet, Stream, 14 | }; 15 | 16 | fn main() -> Result<(), ExitFailure> { 17 | run().map_err(|e| e.into()) 18 | } 19 | 20 | // special "instant" values 21 | const INSTANT_DISABLED: u32 = u32::MAX; 22 | const INSTANT_UNKNOWN: u32 = u32::MAX - 1; 23 | 24 | enum Instant { 25 | Unknown, 26 | Reset, 27 | Known { now: u32, precise: bool }, 28 | } 29 | 30 | fn run() -> Result<(), failure::Error> { 31 | let matches = App::new("excevt") 32 | .about("Pretty prints exception traces contained in an ITM binary dump") 33 | .arg( 34 | Arg::with_name("FILE") 35 | .help("ITM binary dump to process, if omitted stdin will be read") 36 | .required(false) 37 | .index(1), 38 | ) 39 | .arg( 40 | Arg::with_name("follow") 41 | .help("Process appended data as the file grows") 42 | .required(false) 43 | .short("f"), 44 | ) 45 | .arg( 46 | Arg::with_name("timestamp") 47 | .help("Expect timestamps") 48 | .required(false) 49 | .short("t"), 50 | ) 51 | .get_matches(); 52 | 53 | let stdin; 54 | let reader: Box = if let Some(file) = matches.value_of("FILE") { 55 | Box::new(File::open(file)?) 56 | } else { 57 | stdin = io::stdin(); 58 | Box::new(stdin.lock()) 59 | }; 60 | 61 | let stdout = io::stdout(); 62 | let mut stdout = stdout.lock(); 63 | 64 | writeln!(stdout, " TIMESTAMP EXCEPTION")?; 65 | 66 | let mut stream = Stream::new(reader, matches.is_present("follow")); 67 | 68 | const MAX: u32 = 1_000_000_000; 69 | let mut now = if matches.is_present("timestamp") { 70 | // we expect timestamps 71 | INSTANT_UNKNOWN 72 | } else { 73 | // assume that timestamps are disabled 74 | INSTANT_DISABLED 75 | }; 76 | let mut next = None; 77 | 'main: loop { 78 | let packet = if let Some(p) = next.take() { 79 | p 80 | } else { 81 | loop { 82 | match stream.next()? { 83 | Some(Ok(p)) => break p, 84 | 85 | Some(Err(e)) => { 86 | eprintln!("{}", e); 87 | 88 | if now != INSTANT_DISABLED { 89 | // we may have lost a timestamp packet; computed instant is now 90 | // unreliable 91 | now = INSTANT_UNKNOWN; 92 | } 93 | } 94 | 95 | // EOF 96 | None => break 'main, 97 | } 98 | } 99 | }; 100 | 101 | match packet { 102 | Packet::Overflow => { 103 | if now != INSTANT_DISABLED { 104 | // a packet was lost due to limited bandwidth; computed instant is no longer 105 | // reliable 106 | now = INSTANT_UNKNOWN; 107 | } 108 | } 109 | 110 | Packet::ExceptionTrace(et) => { 111 | // if we know we are receiving timestamps ... 112 | if now != INSTANT_DISABLED { 113 | // ... then look ahead for a timestamp 114 | match stream.next()? { 115 | Some(Ok(Packet::LocalTimestamp(lt))) => { 116 | if now == INSTANT_UNKNOWN { 117 | now = 0; 118 | 119 | report(&mut stdout, &et, Instant::Reset)?; 120 | } else { 121 | let precise = lt.is_precise(); 122 | 123 | now = (now + lt.delta()) % MAX; 124 | 125 | report(&mut stdout, &et, Instant::Known { now, precise })?; 126 | } 127 | 128 | continue; 129 | } 130 | 131 | // it's possible to receive two exception traces and then a timestamp 132 | Some(Ok(Packet::ExceptionTrace(et2))) => { 133 | match stream.next()? { 134 | Some(Ok(Packet::LocalTimestamp(lt))) => { 135 | let precise = lt.is_precise(); 136 | 137 | now = (now + lt.delta()) % MAX; 138 | 139 | // first trace has no timestamp so it's imprecise 140 | report( 141 | &mut stdout, 142 | &et, 143 | Instant::Known { 144 | now, 145 | precise: false, 146 | }, 147 | )?; 148 | 149 | report(&mut stdout, &et2, Instant::Known { now, precise })?; 150 | 151 | continue; 152 | } 153 | 154 | // unexpected packet 155 | Some(Ok(packet)) => { 156 | next = Some(packet); 157 | 158 | // fall through: report traces with unknown timestamp 159 | } 160 | 161 | // some byte was lost 162 | Some(Err(e)) => { 163 | eprintln!("{}", e); 164 | 165 | // fall through: report traces with unknown timestamp 166 | } 167 | 168 | // EOF 169 | None => { 170 | // report traces with unknown timestamp 171 | report(&mut stdout, &et, Instant::Unknown)?; 172 | report(&mut stdout, &et2, Instant::Unknown)?; 173 | 174 | break 'main; 175 | } 176 | } 177 | 178 | // report traces with unknown timestamp 179 | report(&mut stdout, &et, Instant::Unknown)?; 180 | report(&mut stdout, &et2, Instant::Unknown)?; 181 | 182 | // computed instant is now unknown 183 | now = INSTANT_UNKNOWN; 184 | 185 | continue; 186 | } 187 | 188 | // unexpected packet 189 | Some(Ok(packet)) => { 190 | next = Some(packet); 191 | 192 | // fall through: report with unknown timestamp 193 | } 194 | 195 | // some byte was lost 196 | Some(Err(e)) => { 197 | eprintln!("{}", e); 198 | 199 | // fall through: report with unknown timestamp 200 | } 201 | 202 | // EOF 203 | None => { 204 | // flush 205 | report(&mut stdout, &et, Instant::Unknown)?; 206 | 207 | break 'main; 208 | } 209 | } 210 | } else { 211 | // fall through: report with unknown timestamp 212 | } 213 | 214 | // report this trace with unknown timestamp 215 | report(&mut stdout, &et, Instant::Unknown)?; 216 | 217 | // computed instant is now unknown 218 | now = INSTANT_UNKNOWN; 219 | } 220 | 221 | Packet::LocalTimestamp(lt) => { 222 | if now == INSTANT_DISABLED { 223 | // first timestamp 224 | now = INSTANT_UNKNOWN; 225 | } else { 226 | if now != INSTANT_DISABLED && lt.delta() == 1_999_999 { 227 | // standalone LTS1 packets are possible when the timestamp counter wraps 228 | // around at the 2 million count 229 | now += 2_000_000; 230 | } else { 231 | // we likely lost a packet; time is now unreliable 232 | now = INSTANT_UNKNOWN; 233 | } 234 | } 235 | } 236 | 237 | _ => { 238 | eprintln!("unexpected packet; exiting"); 239 | 240 | break; 241 | } 242 | } 243 | } 244 | 245 | Ok(()) 246 | } 247 | 248 | fn report(stdout: &mut StdoutLock, et: &ExceptionTrace, now: Instant) -> io::Result<()> { 249 | let f = match et.function() { 250 | Function::Enter => '→', 251 | Function::Exit => '←', 252 | Function::Return => '↓', 253 | }; 254 | 255 | let en = ExceptionNumber(et.number()); 256 | match now { 257 | Instant::Unknown => { 258 | writeln!(stdout, " ????????? {} {}", f, en)?; 259 | } 260 | 261 | Instant::Reset => { 262 | writeln!(stdout, "!000000000 {} {}", f, en)?; 263 | } 264 | 265 | Instant::Known { now, precise } => { 266 | writeln!( 267 | stdout, 268 | "{}{:09} {} {}", 269 | if precise { '=' } else { '<' }, 270 | now, 271 | f, 272 | en 273 | )?; 274 | } 275 | } 276 | Ok(()) 277 | } 278 | 279 | // Adapter for pretty printing the exception number 280 | struct ExceptionNumber(u16); 281 | 282 | impl fmt::Display for ExceptionNumber { 283 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 284 | match self.0 { 285 | 0 => f.write_str("Thread"), 286 | 1 => f.write_str("Reset"), 287 | 2 => f.write_str("NMI"), 288 | 3 => f.write_str("HardFault"), 289 | 4 => f.write_str("MemManage"), 290 | 5 => f.write_str("BusFault"), 291 | 6 => f.write_str("UsageFault"), 292 | 11 => f.write_str("SVCall"), 293 | 12 => f.write_str("DebugMonitor"), 294 | 14 => f.write_str("PendSV"), 295 | 15 => f.write_str("SysTick"), 296 | n => write!(f, "IRQ({})", n - 16), 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/bin/itm-decode.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use std::{ 4 | fs::File, 5 | io::{self, Read}, 6 | }; 7 | 8 | use clap::{App, Arg}; 9 | use exitfailure::ExitFailure; 10 | use itm::{Packet, Stream}; 11 | 12 | fn main() -> Result<(), ExitFailure> { 13 | run().map_err(|e| e.into()) 14 | } 15 | 16 | fn run() -> Result<(), failure::Error> { 17 | let matches = App::new("itm-decode") 18 | .about("Decodes an ITM binary dump into packets") 19 | .arg( 20 | Arg::with_name("FILE") 21 | .help("ITM binary dump to process, if omitted stdin will be read") 22 | .required(false) 23 | .index(1), 24 | ) 25 | .arg( 26 | Arg::with_name("follow") 27 | .help("Process appended data as the file grows") 28 | .required(false) 29 | .short("f"), 30 | ) 31 | .get_matches(); 32 | 33 | let stdin; 34 | let reader: Box = if let Some(file) = matches.value_of("FILE") { 35 | Box::new(File::open(file)?) 36 | } else { 37 | stdin = io::stdin(); 38 | Box::new(stdin.lock()) 39 | }; 40 | 41 | let mut stream = Stream::new(reader, matches.is_present("follow")); 42 | 43 | while let Some(res) = stream.next()? { 44 | match res { 45 | Ok(Packet::DataTraceAddress(dta)) => println!("{:?}", dta), 46 | Ok(Packet::DataTraceDataValue(dtdv)) => println!("{:?}", dtdv), 47 | Ok(Packet::DataTracePcValue(dtpv)) => println!("{:?}", dtpv), 48 | Ok(Packet::EventCounter(ec)) => println!("{:?}", ec), 49 | Ok(Packet::ExceptionTrace(et)) => println!("{:?}", et), 50 | Ok(Packet::GTS1(gts)) => println!("{:?}", gts), 51 | Ok(Packet::GTS2(gts)) => println!("{:?}", gts), 52 | Ok(Packet::Instrumentation(i)) => println!("{:?}", i), 53 | Ok(Packet::LocalTimestamp(lt)) => println!("{:?}", lt), 54 | Ok(Packet::PeriodicPcSample(pps)) => println!("{:?}", pps), 55 | Ok(Packet::StimulusPortPage(spp)) => println!("{:?}", spp), 56 | Ok(Packet::Synchronization(s)) => println!("{:?}", s), 57 | Ok(packet @ Packet::Overflow) => println!("{:?}", packet), 58 | Err(e) => eprintln!("{:?}", e), 59 | } 60 | } 61 | 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /src/bin/pcsampl.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use core::cmp::Ordering; 4 | use std::{ 5 | collections::HashMap, 6 | fs::{self, File}, 7 | }; 8 | 9 | use clap::{App, Arg}; 10 | use exitfailure::ExitFailure; 11 | use failure::bail; 12 | use itm::{Packet, Stream}; 13 | use xmas_elf::{ 14 | sections::SectionData, 15 | symbol_table::{Entry, Type}, 16 | ElfFile, 17 | }; 18 | 19 | fn main() -> Result<(), ExitFailure> { 20 | run().map_err(|e| e.into()) 21 | } 22 | 23 | fn run() -> Result<(), failure::Error> { 24 | let matches = App::new("pcsampl") 25 | .about("ITM-based program profiler") 26 | .arg( 27 | Arg::with_name("elf") 28 | .help("ELF file that corresponds to the profiled program") 29 | .short("e") 30 | .takes_value(true) 31 | .required(true), 32 | ) 33 | .arg( 34 | Arg::with_name("FILE") 35 | .help("ITM binary dump to process") 36 | .required(true) 37 | .index(1), 38 | ) 39 | .get_matches(); 40 | 41 | // collect samples 42 | let mut stream = Stream::new(File::open(matches.value_of("FILE").unwrap())?, false); 43 | 44 | let mut samples = vec![]; 45 | while let Some(res) = stream.next()? { 46 | match res { 47 | Ok(Packet::PeriodicPcSample(pps)) => samples.push(pps), 48 | Ok(_) => {} // don't care 49 | Err(e) => eprintln!("{:?}", e), 50 | } 51 | } 52 | 53 | // extract routines from the ELF file 54 | let data = fs::read(matches.value_of("elf").unwrap())?; 55 | let elf = ElfFile::new(&data).map_err(failure::err_msg)?; 56 | let mut routines = vec![]; 57 | if let Some(section) = elf.find_section_by_name(".symtab") { 58 | match section.get_data(&elf).map_err(failure::err_msg)? { 59 | SectionData::SymbolTable32(entries) => { 60 | for entry in entries { 61 | if entry.get_type() == Ok(Type::Func) { 62 | let name = entry.get_name(&elf).map_err(failure::err_msg)?; 63 | // clear the thumb (T) bit 64 | let address = entry.value() & !1; 65 | let size = entry.size(); 66 | 67 | routines.push(Routine { 68 | address, 69 | name, 70 | size, 71 | }); 72 | } 73 | } 74 | } 75 | _ => bail!("malformed .symtab section"), 76 | } 77 | } else { 78 | bail!(".symtab section is missing") 79 | } 80 | 81 | routines.sort(); 82 | 83 | // map samples to routines 84 | let mut stats = HashMap::new(); 85 | let mut needle = Routine { 86 | address: 0, 87 | name: "", 88 | size: 0, 89 | }; 90 | let min_pc = routines[0].address; 91 | let mut total = samples.len(); 92 | let mut sleep = 0; // sleep cycles 93 | for sample in samples { 94 | if let Some(pc) = sample.pc().map(u64::from) { 95 | if pc < min_pc { 96 | // bogus value; ignore 97 | eprintln!("bogus PC ({:#010x})", pc); 98 | total -= 1; 99 | continue; 100 | } 101 | 102 | needle.address = pc; 103 | let pos = routines.binary_search(&needle).unwrap_or_else(|e| e - 1); 104 | 105 | let hit = &routines[pos]; 106 | if pc > hit.address + hit.size { 107 | // bogus value; ignore 108 | eprintln!("bogus PC ({:#010x})", pc); 109 | total -= 1; 110 | continue; 111 | } 112 | 113 | *stats.entry(hit.name).or_insert(0) += 1; 114 | } else { 115 | sleep += 1; 116 | } 117 | } 118 | 119 | let mut ranking = stats.into_iter().collect::>(); 120 | ranking.sort_by(|a, b| b.1.cmp(&a.1)); 121 | 122 | // report statistics 123 | let pct = |x| 100. * f64::from(x) / total as f64; 124 | println!(" % FUNCTION"); 125 | // we always report sleep time first 126 | println!("{:5.02} *SLEEP*", pct(sleep)); 127 | for entry in ranking { 128 | println!( 129 | "{:5.02} {}", 130 | pct(entry.1), 131 | rustc_demangle::demangle(entry.0).to_string(), 132 | ); 133 | } 134 | 135 | println!("-----\n 100% {} samples", total); 136 | 137 | Ok(()) 138 | } 139 | 140 | #[derive(Clone, Copy, Debug, Eq)] 141 | struct Routine<'a> { 142 | address: u64, 143 | name: &'a str, 144 | size: u64, 145 | } 146 | 147 | impl<'a> Ord for Routine<'a> { 148 | fn cmp(&self, other: &Self) -> Ordering { 149 | self.address.cmp(&other.address) 150 | } 151 | } 152 | 153 | impl<'a> PartialOrd for Routine<'a> { 154 | fn partial_cmp(&self, other: &Self) -> Option { 155 | Some(self.cmp(other)) 156 | } 157 | } 158 | 159 | impl<'a> PartialEq for Routine<'a> { 160 | fn eq(&self, other: &Self) -> bool { 161 | self.address == other.address 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/bin/port-demux.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use std::{ 4 | collections::BTreeMap, 5 | fs::File, 6 | io::{self, Read, Write}, 7 | }; 8 | 9 | use clap::{App, Arg}; 10 | use exitfailure::ExitFailure; 11 | use itm::{Packet, Stream}; 12 | 13 | fn main() -> Result<(), ExitFailure> { 14 | run().map_err(|e| e.into()) 15 | } 16 | 17 | fn run() -> Result<(), failure::Error> { 18 | let matches = App::new("port-demux") 19 | .about("Demuxes instrumentation packets") 20 | .arg( 21 | Arg::with_name("FILE") 22 | .help("ITM binary dump to process, if omitted stdin will be read") 23 | .required(false) 24 | .index(1), 25 | ) 26 | .arg( 27 | Arg::with_name("follow") 28 | .help("Process appended data as the file grows") 29 | .required(false) 30 | .short("f"), 31 | ) 32 | .get_matches(); 33 | 34 | let stdin; 35 | let reader: Box = if let Some(file) = matches.value_of("FILE") { 36 | Box::new(File::open(file)?) 37 | } else { 38 | stdin = io::stdin(); 39 | Box::new(stdin.lock()) 40 | }; 41 | 42 | let mut stream = Stream::new(reader, matches.is_present("follow")); 43 | 44 | let mut sinks = BTreeMap::new(); 45 | while let Some(res) = stream.next()? { 46 | match res { 47 | Ok(Packet::Instrumentation(ip)) => { 48 | let port = ip.port(); 49 | let payload = ip.payload(); 50 | 51 | let sink = if let Some(sink) = sinks.get_mut(&port) { 52 | sink 53 | } else { 54 | let f = File::create(format!("{}.stim", port))?; 55 | sinks.insert(port, f); 56 | sinks.get_mut(&port).unwrap() 57 | }; 58 | 59 | sink.write_all(payload)?; 60 | } 61 | Ok(_) => {} // don't care 62 | Err(e) => eprintln!("{:?}", e), 63 | } 64 | } 65 | 66 | Ok(()) 67 | } 68 | --------------------------------------------------------------------------------