From 7715cb69aa727f539f2d7dbe1b8421ca19b1b323 Mon Sep 17 00:00:00 2001 From: Diego Asanza Date: Tue, 31 Dec 2024 12:09:46 +0100 Subject: [PATCH] Implement basic gdb server functionality. This commit introduces basic gdbserver support to the zmu ARM simulator, enabling remote debugging with a gdb client. The following features are implemented: * Breakpoints: Users can set, clear, and manage breakpoints during program execution. * Continue: Execution can be resumed from the current breakpoint or paused state. * Step Instruction: Users can step through program instructions for detailed debugging. This functionality significantly enhances the debugging capabilities of zmu, making it more versatile for developers. To start the gdbserver just call zmu with the --gdb flag: $ zmu.exe run --gdb binary.elf A gdb server will be open on localhost port 9001 Signed-off-by: Diego Asanza --- Cargo.lock | 341 ++++++++++++++++----------- src/main.rs | 26 +++ src/main.rs.orig | 356 +++++++++++++++++++++++++++++ zmu_cortex_m/Cargo.toml | 2 + zmu_cortex_m/src/gdb/conn.rs | 90 ++++++++ zmu_cortex_m/src/gdb/mod.rs | 8 + zmu_cortex_m/src/gdb/server.rs | 171 ++++++++++++++ zmu_cortex_m/src/gdb/simulation.rs | 179 +++++++++++++++ zmu_cortex_m/src/gdb/target.rs | 244 ++++++++++++++++++++ zmu_cortex_m/src/lib.rs | 1 + 10 files changed, 1283 insertions(+), 135 deletions(-) create mode 100644 src/main.rs.orig create mode 100644 zmu_cortex_m/src/gdb/conn.rs create mode 100644 zmu_cortex_m/src/gdb/mod.rs create mode 100644 zmu_cortex_m/src/gdb/server.rs create mode 100644 zmu_cortex_m/src/gdb/simulation.rs create mode 100644 zmu_cortex_m/src/gdb/target.rs diff --git a/Cargo.lock b/Cargo.lock index b3d97c1..fb95895 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,27 +1,42 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -34,59 +49,65 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bumpalo" version = "3.16.0" @@ -95,15 +116,18 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.0.73" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -113,32 +137,32 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ + "android-tzdata", + "iana-time-zone", "js-sys", - "libc", - "num-integer", "num-traits", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] name = "clap" -version = "4.5.19" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -148,15 +172,21 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "enum-set" @@ -174,11 +204,35 @@ dependencies = [ "version_check", ] +[[package]] +name = "gdbstub" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c683a9f13de31432e6097131d5f385898c7f0635c0f392b9d0fa165063c8ac" +dependencies = [ + "bitflags", + "cfg-if", + "log", + "managed", + "num-traits", + "paste", +] + +[[package]] +name = "gdbstub_arch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328a9e9425db13770d0d11de6332a608854266e44c53d12776be7b4aa427e3de" +dependencies = [ + "gdbstub", + "num-traits", +] + [[package]] name = "gimli" -version = "0.26.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "goblin" @@ -197,6 +251,29 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -205,7 +282,7 @@ checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -216,76 +293,70 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.126" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "log" -version = "0.4.17" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "memchr" -version = "2.5.0" +name = "managed" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] -name = "miniz_oxide" -version = "0.5.3" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "num-integer" -version = "0.1.45" +name = "miniz_oxide" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ - "autocfg", - "num-traits", + "adler2", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" -version = "0.29.0" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.13.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pad" @@ -296,6 +367,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "plain" version = "0.2.3" @@ -304,27 +381,27 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "scroll" @@ -343,9 +420,15 @@ checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "stderrlog" version = "0.6.0" @@ -367,20 +450,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.79" +version = "2.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" dependencies = [ "proc-macro2", "quote", @@ -389,9 +461,9 @@ dependencies = [ [[package]] name = "tabwriter" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111" +checksum = "a327282c4f64f6dc37e3bba4c2b6842cc3a992f204fa58d917696a89f691e5f6" dependencies = [ "unicode-width", ] @@ -407,24 +479,25 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "unicode-ident" -version = "1.0.2" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "utf8parse" @@ -434,40 +507,40 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 1.0.98", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -475,59 +548,55 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] -name = "winapi" -version = "0.3.9" +name = "winapi-util" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-sys 0.59.0", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "winapi", + "windows-targets", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] @@ -616,4 +685,6 @@ version = "0.1.0" dependencies = [ "byteorder", "enum-set", + "gdbstub", + "gdbstub_arch", ] diff --git a/src/main.rs b/src/main.rs index 96c4c5c..341b5da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,7 @@ use zmu_cortex_m::Processor; use zmu_cortex_m::system::simulation::simulate_trace; use zmu_cortex_m::system::simulation::{simulate, SimulationError}; +use zmu_cortex_m::gdb::server::GdbServer; mod errors { // Create the Error, ErrorKind, ResultExt, and Result types @@ -59,6 +60,7 @@ fn run_bin( trace: bool, option_trace_start: Option, itm_file: Option>, + gdb: bool, ) -> Result { let res = Object::parse(buffer).unwrap(); @@ -122,6 +124,22 @@ fn run_bin( let trace_start = option_trace_start.unwrap_or(0); let semihost_func = Box::new(get_semihost_func(Instant::now())); + if gdb { + let gdb = GdbServer::new( + &flash_mem, + semihost_func, + if flash_start_address != 0 { + Some(MemoryMapConfig::new(flash_start_address, 0, flash_size)) + } else { + None + }, + flash_size, + ); + + let exit_code = gdb?.start().expect("GDB server failed"); + return Ok(exit_code); + } + let statistics = if trace { debug!("Configuring tracing."); @@ -234,6 +252,7 @@ fn run(args: &ArgMatches) -> Result { run_matches.get_flag("trace"), trace_start, itm_output, + run_matches.get_flag("gdb"), )? } Some((_, _)) => unreachable!(), @@ -289,6 +308,13 @@ fn main() { .help("List of free arguments to pass to runtime as parameters") .index(2) .action(ArgAction::Append), + ) + .arg( + Arg::new("gdb") + .action(ArgAction::SetTrue) + .long("gdb") + .help("Enable the gdb server") + .num_args(0) ), ) .get_matches(); diff --git a/src/main.rs.orig b/src/main.rs.orig new file mode 100644 index 0000000..519fefe --- /dev/null +++ b/src/main.rs.orig @@ -0,0 +1,356 @@ +#![recursion_limit = "1024"] + +#[macro_use] +extern crate error_chain; + +extern crate clap; +extern crate goblin; +extern crate pad; +extern crate tabwriter; +extern crate zmu_cortex_m; + +#[macro_use] +extern crate log; +extern crate stderrlog; + +use clap::value_parser; +use clap::Arg; +use clap::ArgAction; +use clap::ArgMatches; +use clap::Command; +use goblin::elf::program_header::pt_to_str; +use goblin::Object; +use std::fs::File; +use std::io; +use std::io::prelude::*; +use std::time::Instant; + +mod semihost; +mod trace; + +use crate::semihost::get_semihost_func; +use crate::trace::format_trace_entry; + +use std::cmp; +use std::collections::HashMap; +use tabwriter::TabWriter; +use zmu_cortex_m::memory::map::MemoryMapConfig; +use zmu_cortex_m::Processor; + +use zmu_cortex_m::system::simulation::simulate_trace; +use zmu_cortex_m::system::simulation::{simulate, SimulationError}; +use zmu_cortex_m::gdb::server::GdbServer; + +mod errors { + // Create the Error, ErrorKind, ResultExt, and Result types + error_chain! {} +} + +use crate::errors::*; +use error_chain::State; + +impl From for errors::Error { + fn from(_error: SimulationError) -> Self { + errors::Error(ErrorKind::Msg("trap".to_string()), State::default()) + } +} + +fn run_bin( + buffer: &[u8], + trace: bool, + option_trace_start: Option, + itm_file: Option>, +<<<<<<< HEAD +) -> Result { +======= + gdb: bool, +) -> Result<()> { +>>>>>>> a5fb3d6 (implement simulation module) + let res = Object::parse(buffer).unwrap(); + + let elf = match res { + Object::Elf(elf) => elf, + _ => { + bail!("Unsupported file format."); + } + }; + + debug!("Detected ELF file."); + + // auto detection of required flash size: + // loop 1: determine lower bound and upper bound + + let mut min_address = 0xffff_ffff; + let mut max_address = 0; + debug!("Determining ELF code sections"); + for ph in &elf.program_headers { + if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_filesz > 0 { + let dst_addr = ph.p_paddr as usize; + let dst_end_addr = (ph.p_paddr + ph.p_filesz) as usize; + + debug!( + "PT_LOAD section at 0x{:08x} - 0x{:08x} (size = {} bytes)", + dst_addr, dst_end_addr, ph.p_filesz + ); + min_address = cmp::min(dst_addr, min_address); + max_address = cmp::max(dst_end_addr, max_address); + } else { + debug!( + "ignoring section : {} (size = {} bytes)", + pt_to_str(ph.p_type), + ph.p_filesz + ); + } + } + + let flash_start_address = min_address as u32; + let flash_size = max_address - min_address; + info!( + "Auto configuring flash: address space is 0x{:x}..0x{:x}, size= {} bytes", + flash_start_address, max_address, flash_size + ); + let mut flash_mem = vec![0; flash_size]; + + // loop 2: load data by offset + for ph in &elf.program_headers { + if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_filesz > 0 { + let dst_addr = (ph.p_paddr - u64::from(flash_start_address)) as usize; + let dst_end_addr = + ((ph.p_paddr + ph.p_filesz) - u64::from(flash_start_address)) as usize; + + let src_addr = ph.p_offset as usize; + let src_end_addr = (ph.p_offset + ph.p_filesz) as usize; + + flash_mem[dst_addr..dst_end_addr].copy_from_slice(&buffer[src_addr..src_end_addr]); + } + } + + let trace_start = option_trace_start.unwrap_or(0); + let semihost_func = Box::new(get_semihost_func(Instant::now())); + + if gdb { + let gdb = GdbServer::new( + &flash_mem, + if flash_start_address != 0 { + Some(MemoryMapConfig::new(flash_start_address, 0, flash_size)) + } else { + None + }, + flash_size, + ); + + gdb?.start().unwrap(); + return Ok(()); + } + + let statistics = if trace { + debug!("Configuring tracing."); + + let mut symboltable = HashMap::new(); + let mut trace_stdout = TabWriter::new(io::stdout()).minwidth(16).padding(1); + + for sym in &elf.syms { + if sym.st_type() != goblin::elf::sym::STT_FILE { + if let Some(maybe_name) = elf.strtab.get_at(sym.st_name) { + let name = maybe_name; + let mut count = 0; + let mut pos = sym.st_value as u32; + while count <= sym.st_size { + // Align addresses to 2 byte alignment + symboltable.insert(pos & 0xffff_fffe, name); + pos += 2; + count += 2; + } + } + } + } + + let tracefunc = |processor: &Processor| { + if processor.instruction_count >= trace_start { + let trace_entry = format_trace_entry(processor, &symboltable); + writeln!(&mut trace_stdout, "{}", trace_entry).unwrap(); + let _ = trace_stdout.flush(); + } + }; + debug!("Starting simulation with trace."); + + simulate_trace( + &flash_mem, + tracefunc, + semihost_func, + itm_file, + if flash_start_address != 0 { + Some(MemoryMapConfig::new(flash_start_address, 0, flash_size)) + } else { + None + }, + flash_size, + )? + } else { + debug!("Starting simulation."); + simulate( + &flash_mem, + semihost_func, + itm_file, + if flash_start_address != 0 { + Some(MemoryMapConfig::new(flash_start_address, 0, flash_size)) + } else { + None + }, + flash_size, + )? + }; + + let duration_in_secs = statistics.duration.as_secs() as f64 + + (f64::from(statistics.duration.subsec_nanos()) / 1_000_000_000f64); + let instructions_per_sec = statistics.instruction_count as f64 / duration_in_secs; + + let cycles_per_sec = statistics.cycle_count as f64 / duration_in_secs; + + debug!("Simulation done."); + + info!( + "{:?}, {} instructions, {:.0} instructions per sec, {:.0} cycles_per_sec ~ {:.2} Mhz", + statistics.duration, + statistics.instruction_count, + instructions_per_sec, + cycles_per_sec, + cycles_per_sec / 1_000_000.0, + ); + Ok(statistics.exit_code) +} + +fn open_itm_file(filename: &str) -> Option> { + let result = File::create(filename); + + match result { + Ok(f) => Some(Box::new(f) as Box), + Err(_) => None, + } +} + +fn run(args: &ArgMatches) -> Result { + let exit_code = match args.subcommand() { + Some(("run", run_matches)) => { + let filename = run_matches + .get_one::("EXECUTABLE") + .chain_err(|| "filename missing")?; + + let trace_start = run_matches.get_one::("trace-start").copied(); + + let itm_output = match run_matches.get_one::("itm") { + Some(filename) => open_itm_file(filename), + None => None, + }; + + let buffer = { + let mut v = Vec::new(); + let mut f = File::open(filename).chain_err(|| "unable to open file")?; + f.read_to_end(&mut v).chain_err(|| "failed to read file")?; + v + }; + + run_bin( + &buffer, + run_matches.get_flag("trace"), + trace_start, + itm_output, +<<<<<<< HEAD + )? +======= + run_matches.get_flag("gdb"), + )?; +>>>>>>> a5fb3d6 (implement simulation module) + } + Some((_, _)) => unreachable!(), + None => unreachable!(), // If all subcommands are defined above, anything else is unreachabe!() + }; + + Ok(exit_code) +} + +fn main() { + let cmd = Command::new("zmu") + .bin_name("zmu") + .arg( + Arg::new("verbosity") + .short('v') + .help("Increase message verbosity") + .action(ArgAction::Count), + ) + .about("a Low level emulator for microcontrollers") + .subcommand_required(true) + .subcommand( + Command::new("run") + .about("Load and run ") + .arg( + Arg::new("trace") + .action(ArgAction::SetTrue) + .short('t') + .long("trace") + .help("Print instruction trace to stdout"), + ) + .arg( + Arg::new("trace-start") + .long("trace-start") + .help("Instruction on which to start tracing") + .action(ArgAction::Set) + .value_parser(value_parser!(u64)), + ) + .arg( + Arg::new("itm") + .long("itm") + .help("Name of file to which itm trace data is written to. ") + .num_args(1), + ) + .arg( + Arg::new("EXECUTABLE") + .index(1) + .help("Set executable to load") + .required(true), + ) + .arg( + Arg::new("ARGS") + .required(false) + .help("List of free arguments to pass to runtime as parameters") + .index(2) + .action(ArgAction::Append), + ) + .arg( + Arg::new("gdb") + .action(ArgAction::SetTrue) + .long("gdb") + .help("Enable the gdb server") + .num_args(0) + ), + ) + .get_matches(); + + let verbose = cmd.get_count("verbosity") as usize; + + stderrlog::new() + .module(module_path!()) + .verbosity(verbose) + .init() + .unwrap(); + + let result = run(&cmd); + match result { + Ok(exit_code) => { + std::process::exit(exit_code as i32); + } + Err(ref e) => { + error!("error: {}", e); + + for e in e.iter().skip(1) { + error!("caused by: {}", e); + } + + if let Some(backtrace) = e.backtrace() { + error!("backtrace: {:?}", backtrace); + } + + ::std::process::exit(1); + } + } +} diff --git a/zmu_cortex_m/Cargo.toml b/zmu_cortex_m/Cargo.toml index a9889fb..d5425a4 100644 --- a/zmu_cortex_m/Cargo.toml +++ b/zmu_cortex_m/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] byteorder = "1" enum-set = "0.0.8" +gdbstub = "0.7" +gdbstub_arch = "0.3" [features] diff --git a/zmu_cortex_m/src/gdb/conn.rs b/zmu_cortex_m/src/gdb/conn.rs new file mode 100644 index 0000000..87bc35c --- /dev/null +++ b/zmu_cortex_m/src/gdb/conn.rs @@ -0,0 +1,90 @@ +use gdbstub::conn::{Connection, ConnectionExt}; +use std::{ + io::Read, net::{Shutdown, TcpListener, TcpStream}, str +}; + +pub struct TcpConnection { + stream: TcpStream, +} + +impl TcpConnection { + pub fn new_localhost(port: u16) -> Result { + let listener = TcpListener::bind(("127.0.0.1", port)).unwrap(); + + for stream in listener.incoming() { + let stream = stream.unwrap(); + stream.set_read_timeout(Some(std::time::Duration::from_millis(1))) + .expect("set_read_timeout call failed"); + // stream.set_nonblocking(true).expect("set_nonblocking call failed"); + return Ok(TcpConnection { stream }); + }; + + Err("could not accept socket connection") + } +} + +impl Drop for TcpConnection { + fn drop(&mut self) { + self.stream.shutdown(Shutdown::Both).expect("shutdown failed"); + } +} + +impl Connection for TcpConnection { + type Error = &'static str; + + fn write(&mut self, b: u8) -> Result<(), &'static str> { + match self.stream.write(b) { + Ok(_) => Ok(()), + Err(_) => Err("socket write failed") + } + } + + fn flush(&mut self) -> Result<(), &'static str> { + match self.stream.flush() { + Ok(_) => Ok(()), + Err(_) => Err("socket flush failed") + } + } +} + +impl ConnectionExt for TcpConnection { + + fn read(&mut self) -> std::result::Result { + let mut buf: [u8; 1] = [0]; + loop { + match self.stream.read_exact(&mut buf) + { + Ok(_) => break, + Err(e) => match e.kind() { + #[cfg(windows)] + std::io::ErrorKind::TimedOut => continue, + #[cfg(unix)] + std::io::ErrorKind::WouldBlock => continue, + _ => return Err("socket read failed") + } + } + } + Ok(buf[0]) + } + + fn peek(&mut self) -> std::result::Result, Self::Error> { + let mut buf: [u8; 1] = [0]; + loop { + match self.stream.peek(&mut buf) + { + Ok(_) => break, + Err(e) => match e.kind() { + #[cfg(windows)] + std::io::ErrorKind::TimedOut => return Ok(None), + #[cfg(unix)] + std::io::ErrorKind::WouldBlock => return Ok(None), + _ => { + println!("peek error: {:?}", e); + return Err("socket peek failed") + } + } + } + } + Ok(Some(buf[0])) + } +} \ No newline at end of file diff --git a/zmu_cortex_m/src/gdb/mod.rs b/zmu_cortex_m/src/gdb/mod.rs new file mode 100644 index 0000000..393973a --- /dev/null +++ b/zmu_cortex_m/src/gdb/mod.rs @@ -0,0 +1,8 @@ +//! +//! Gdb Module +//! + +pub mod server; +mod conn; +mod simulation; +mod target; \ No newline at end of file diff --git a/zmu_cortex_m/src/gdb/server.rs b/zmu_cortex_m/src/gdb/server.rs new file mode 100644 index 0000000..0fee164 --- /dev/null +++ b/zmu_cortex_m/src/gdb/server.rs @@ -0,0 +1,171 @@ +//! +//! Flash Memory simulation +//! +//! + +use gdbstub::stub::run_blocking; +use gdbstub::stub::SingleThreadStopReason; +use gdbstub::conn::Connection; +use gdbstub::conn::ConnectionExt; +use gdbstub::target::Target; +use gdbstub::common::Signal; +use gdbstub::stub::GdbStub; +use gdbstub::stub::DisconnectReason; + +use crate::{MemoryMapConfig}; +use crate::gdb::conn; +use conn::TcpConnection; +use crate::gdb::simulation::SimulationEvent; +use crate::gdb::simulation::SimulationRunEvent; + +use crate::gdb::target::ZmuTarget; + +use crate::semihosting::SemihostingCommand; +use crate::semihosting::SemihostingResponse; + +/// +/// The gdb Server +/// +pub struct GdbServer { + // number: i32, + target: ZmuTarget, +} + +/// +impl GdbServer { + + /// + pub fn new( + code: &[u8], + semihost_func: Box SemihostingResponse + 'static>, + map: Option, + flash_size: usize, + ) -> Result { + + let target = ZmuTarget::new(code, semihost_func, map, flash_size); + + Ok(GdbServer {target}) + } + + /// + pub fn start(&mut self) -> Result { + println!("Starting GDB server"); + let mut exit_code = 0; + let conn = match conn::TcpConnection::new_localhost(9001) { + Ok(conn) => conn, + Err(e) => return Err(e), + }; + + let gdb = GdbStub::new(conn); + + match gdb.run_blocking::(&mut self.target) { + Ok(disconnect_reason) => match disconnect_reason { + DisconnectReason::Disconnect => { + println!("GDB client has disconnected. Running to completion..."); + loop { + match self.target.step() { + SimulationEvent::Halted => break, + SimulationEvent::Finalized(code) => { + exit_code = code; + break; + } + _ => {} + } + } + } + DisconnectReason::TargetExited(code) => { + println!("\nTarget exited with code {}!", code) + } + DisconnectReason::TargetTerminated(sig) => { + println!("\nTarget terminated with signal {}!", sig) + } + DisconnectReason::Kill => println!("\nGDB sent a kill command!"), + }, + Err(e) => { + if e.is_target_error() { + println!( + "target encountered a fatal error: {}", + e.into_target_error().unwrap() + ) + } else if e.is_connection_error() { + let (e, kind) = e.into_connection_error().unwrap(); + println!("connection error: {:?} - {}", kind, e,) + } else { + println!("gdbstub encountered a fatal error: {}", e) + } + } + } + Ok(exit_code) + } +} + + +enum EventLoop {} + +impl run_blocking::BlockingEventLoop for EventLoop { + type Target = ZmuTarget; + type Connection = TcpConnection; + type StopReason = SingleThreadStopReason; + + #[allow(clippy::type_complexity)] + fn wait_for_stop_reason( + target: &mut ZmuTarget, + conn: &mut Self::Connection, + ) -> Result< + run_blocking::Event>, + run_blocking::WaitForStopReasonError< + ::Error, + ::Error, + >, + > { + + let poll_incoming_data = || { + // gdbstub takes ownership of the underlying connection, so the `borrow_conn` + // method is used to borrow the underlying connection back from the stub to + // check for incoming data. + conn.peek().map(|b| b.is_some()).unwrap_or(true) + }; + + match target.run(poll_incoming_data) { + SimulationRunEvent::IncomingData => { + let byte = conn + .read() + .map_err(run_blocking::WaitForStopReasonError::Connection)?; + Ok(run_blocking::Event::IncomingData(byte)) + } + SimulationRunEvent::Event(event) => { + use gdbstub::target::ext::breakpoints::WatchKind; + + // translate emulator stop reason into GDB stop reason + let stop_reason = match event { + SimulationEvent::DoneStep => SingleThreadStopReason::DoneStep, + SimulationEvent::Halted => SingleThreadStopReason::Terminated(Signal::SIGSTOP), + SimulationEvent::Break => SingleThreadStopReason::SwBreak(()), + SimulationEvent::WatchWrite(addr) => SingleThreadStopReason::Watch { + tid: (), + kind: WatchKind::Write, + addr, + }, + SimulationEvent::WatchRead(addr) => SingleThreadStopReason::Watch { + tid: (), + kind: WatchKind::Read, + addr, + }, + SimulationEvent::Finalized(exit_code) => SingleThreadStopReason::Exited(exit_code as u8), + }; + + Ok(run_blocking::Event::TargetStopped(stop_reason)) + } + } + } + + fn on_interrupt( + _target: &mut ZmuTarget, + ) -> Result>, ::Error> { + // Because this emulator runs as part of the GDB stub loop, there isn't any + // special action that needs to be taken to interrupt the underlying target. It + // is implicitly paused whenever the stub isn't within the + // `wait_for_stop_reason` callback. + Ok(Some(SingleThreadStopReason::Signal(Signal::SIGINT))) + } +} diff --git a/zmu_cortex_m/src/gdb/simulation.rs b/zmu_cortex_m/src/gdb/simulation.rs new file mode 100644 index 0000000..6762090 --- /dev/null +++ b/zmu_cortex_m/src/gdb/simulation.rs @@ -0,0 +1,179 @@ +//! +//! Cortex System simulation +//! + + +use crate::system::simulation::SimulationError; +use crate::Processor; +use crate::MemoryMapConfig; +use crate::executor::Executor; +use crate::core::reset::Reset; +use crate::core::register::BaseReg; + +use crate::semihosting::SemihostingCommand; +use crate::semihosting::SemihostingResponse; + +/// +/// Cortex ystem simulation framework +/// +pub struct Simulation { + /// processor state + pub processor: Processor, + /// + pub exec_mode: SimulationExecMode, + /// + pub watchpoints: Vec, + /// + pub breakpoints: Vec, +} + +/// +/// +/// +pub enum SimulationEvent { + /// + DoneStep, + /// + #[allow(dead_code)] + Halted, + /// + Break, + /// + #[allow(dead_code)] + WatchWrite(u32), + /// + WatchRead(u32), + /// + Finalized(u32) +} + +/// +/// +/// +pub enum SimulationExecMode { + /// + Step, + /// + Continue, + /// + RangeStep(u32, u32), +} + +impl Simulation { + /// + /// Prepare a new simulation instance + /// + pub fn new(code: &[u8], + semihost_func: Box SemihostingResponse + 'static>, + // itm_file: Option>, + map: Option, + flash_size: usize, + ) -> Result { + let mut processor = Processor::new(); + // processor.itm(itm_file); + processor.semihost(Some(semihost_func)); + processor.memory_map(map); + processor.flash_memory(flash_size, code); + //processor.ram_memory(ram_size); + processor.cache_instructions(); + processor.running = true; // running + match processor.reset() { + Ok(_) => {}, + Err(_) => return Err("Error resetting processor"), + } + + Ok(Simulation { + processor, + exec_mode: SimulationExecMode::Step, + watchpoints: Vec::new(), + breakpoints: Vec::new(), + }) + } + + /// + pub fn run(&mut self, mut poll_incomming_data: impl FnMut() -> bool ) -> SimulationRunEvent { + match self.exec_mode { + SimulationExecMode::Step => { + return SimulationRunEvent::Event(self.step()); + }, + SimulationExecMode::Continue => { + let mut cycles = 0; + loop { + if cycles % 1024 == 0 { + if poll_incomming_data() { + return SimulationRunEvent::IncomingData; + } + } + cycles += 1; + let evt = self.step(); + match evt { + SimulationEvent::DoneStep => {}, + _ => return SimulationRunEvent::Event(evt), + }; + } + }, + SimulationExecMode::RangeStep(start, end) => { + let mut cycles = 0; + loop { + if cycles % 1024 == 0 { + if poll_incomming_data() { + return SimulationRunEvent::IncomingData; + } + } + cycles += 1; + + let evt = self.step(); + + match evt { + SimulationEvent::DoneStep => { + if !(start..end).contains(&self.processor.get_pc()) { + return SimulationRunEvent::Event(evt); + } + }, + _ => return SimulationRunEvent::Event(evt), + }; + } + }, + } + } + + /// + /// + /// + pub fn reset(&mut self) -> Result<(), SimulationError> { + match self.processor.reset() { + Ok(_) => Ok(()), + Err(_) => Err(SimulationError::FaultTrap), + } + } + + /// + /// + /// + pub fn step(&mut self) -> SimulationEvent { + if self.processor.running { + self.processor.step(); + } else if self.processor.sleeping { + self.processor.step_sleep(); + } if self.breakpoints.contains(&self.processor.get_pc()) { + return SimulationEvent::Break; + } if self.watchpoints.contains(&self.processor.get_pc()) { + return SimulationEvent::WatchRead(self.processor.get_pc()); + } if !self.processor.running && !self.processor.sleeping { + return SimulationEvent::Finalized(self.processor.exit_code); + } else { + return SimulationEvent::DoneStep; + } + } +} + +/// +/// +/// + +pub enum SimulationRunEvent { + /// + IncomingData, + /// + Event(SimulationEvent), +} diff --git a/zmu_cortex_m/src/gdb/target.rs b/zmu_cortex_m/src/gdb/target.rs new file mode 100644 index 0000000..6462983 --- /dev/null +++ b/zmu_cortex_m/src/gdb/target.rs @@ -0,0 +1,244 @@ +use gdbstub::target::Target; +use gdbstub::target::TargetResult; +use gdbstub::target; +use gdbstub::target::ext::monitor_cmd::MonitorCmd; +use gdbstub::common::Signal; +use gdbstub::outputln; + +use crate::MemoryMapConfig; +use crate::gdb::simulation; + +use gdbstub::target::ext::base::singlethread::SingleThreadBase; +use gdbstub::target::ext::base::singlethread::SingleThreadResume; +use gdbstub::target::ext::base::singlethread::SingleThreadSingleStep; +use gdbstub::target::ext::base::singlethread::SingleThreadSingleStepOps; +use gdbstub::target::ext::base::singlethread::SingleThreadResumeOps; +use gdbstub::target::ext::base::singlethread::SingleThreadRangeStepping; +use gdbstub::target::ext::base::singlethread::SingleThreadRangeSteppingOps; + +use crate::gdb::simulation::SimulationRunEvent; +use crate::gdb::simulation::SimulationEvent; + +use crate::core::register::Reg; +use crate::core::register::BaseReg; + +use crate::semihosting::SemihostingCommand; +use crate::semihosting::SemihostingResponse; + +pub struct ZmuTarget { + simulation: simulation::Simulation, +} + +impl ZmuTarget { + pub fn new( + code: &[u8], + semihost_func: Box SemihostingResponse + 'static>, + map: Option, + flash_size: usize, + ) -> ZmuTarget { + let simulation = simulation::Simulation::new(code, semihost_func, map, flash_size); + ZmuTarget { + simulation: simulation.unwrap(), + } + } + + pub fn run(&mut self, poll_incomming_data: impl FnMut() -> bool ) -> SimulationRunEvent { + self.simulation.run(poll_incomming_data) + } + + // pub fn reset(&mut self) -> Result<(), &'static str> { + // self.simulation.reset(); + + // } + + pub fn step(&mut self) -> SimulationEvent { + self.simulation.step() + } +} + +impl Target for ZmuTarget { + type Arch = gdbstub_arch::arm::Armv4t; + type Error = &'static str; + + #[inline(always)] + fn base_ops(&mut self) -> target::ext::base::BaseOps<'_, Self::Arch, Self::Error> { + target::ext::base::BaseOps::SingleThread(self) + } + + #[inline(always)] + fn use_no_ack_mode(&self) -> bool { + true + } + + #[inline(always)] + fn use_x_upcase_packet(&self) -> bool { + true + } + + #[inline(always)] + fn support_monitor_cmd(&mut self) -> Option> { + Some(self) + } + + #[inline(always)] + fn support_breakpoints( + &mut self, + ) -> Option> { + Some(self) + } +} + +impl SingleThreadBase for ZmuTarget { + fn read_registers( + &mut self, + regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, + ) -> TargetResult<(), Self> { + print!("> read_registers"); + regs.r = self.simulation.processor.r0_12; + regs.sp = self.simulation.processor.get_r(Reg::SP); + regs.lr = self.simulation.processor.lr; + regs.pc = self.simulation.processor.get_pc(); + regs.cpsr = self.simulation.processor.cfsr; + Ok(()) + } + + #[inline(never)] + fn write_registers( + &mut self, + _regs: &gdbstub_arch::arm::reg::ArmCoreRegs + ) -> TargetResult<(), Self> { + print!("> write_registers"); + Ok(()) + } + + #[inline(never)] + fn read_addrs( + &mut self, + _start_addr: u32, + data: &mut [u8], + ) -> TargetResult { + print!("> read_addrs"); + data.iter_mut().for_each(|b| *b = 0x55); + Ok(data.len()) + } + + #[inline(never)] + fn write_addrs( + &mut self, + _start_addr: u32, + _data: &[u8], + ) -> TargetResult<(), Self> { + print!("> write_addrs"); + Ok(()) + } + + #[inline(always)] + fn support_resume( + &mut self, + ) -> Option> { + Some(self) + } +} + +impl SingleThreadResume for ZmuTarget { + #[inline(never)] + fn resume(&mut self, _signal: Option) -> Result<(), Self::Error> { + self.simulation.exec_mode = simulation::SimulationExecMode::Continue; + Ok(()) + } + + #[inline(always)] + fn support_single_step( + &mut self + ) -> Option> { + self.simulation.exec_mode = simulation::SimulationExecMode::Step; + Some(self) + } + + #[inline(always)] + fn support_range_step(&mut self) -> Option> { + Some(self) + } +} + +impl SingleThreadSingleStep for ZmuTarget { + #[inline(never)] + fn step(&mut self, _signal: Option) -> Result<(), Self::Error> { + self.simulation.exec_mode = simulation::SimulationExecMode::Step; + Ok(()) + } +} + +impl SingleThreadRangeStepping for ZmuTarget { + #[inline(never)] + fn resume_range_step( + &mut self, + start: u32, + end: u32, + ) -> Result<(), Self::Error> { + self.simulation.exec_mode = simulation::SimulationExecMode::RangeStep(start, end); + Ok(()) + } + +} + + +impl target::ext::breakpoints::Breakpoints for ZmuTarget { + #[inline(always)] + fn support_sw_breakpoint( + &mut self, + ) -> Option> { + Some(self) + } +} + +impl target::ext::breakpoints::SwBreakpoint for ZmuTarget { + #[inline(never)] + fn add_sw_breakpoint( + &mut self, + addr: u32, + _kind: gdbstub_arch::arm::ArmBreakpointKind, + ) -> TargetResult { + self.simulation.breakpoints.push(addr); + Ok(true) + } + + #[inline(never)] + fn remove_sw_breakpoint( + &mut self, + addr: u32, + _kind: gdbstub_arch::arm::ArmBreakpointKind, + ) -> TargetResult { + self.simulation.breakpoints.retain(|&x| x != addr); + Ok(true) + } +} + +impl MonitorCmd for ZmuTarget { + #[inline(never)] + fn handle_monitor_cmd( + &mut self, + cmd: &[u8], + mut out: gdbstub::target::ext::monitor_cmd::ConsoleOutput<'_>, + ) -> Result<(), Self::Error> { + print!("> handle_monitor_cmd {:?}", cmd); + let cmd = core::str::from_utf8(cmd).map_err(|_| "Invalid UTF-8")?; + match cmd { + "reset" => { + match self.simulation.reset() { + Ok(_) => { + outputln!(out, "Target reset"); + }, + Err(_) => { + outputln!(out, "Error resetting target"); + return Err("Error resetting target"); + } + } + } + _ => { + outputln!(out, "Unknown command: {:?}", cmd); + } + } + Ok(()) + } +} diff --git a/zmu_cortex_m/src/lib.rs b/zmu_cortex_m/src/lib.rs index 1ad605e..bc4f097 100644 --- a/zmu_cortex_m/src/lib.rs +++ b/zmu_cortex_m/src/lib.rs @@ -37,6 +37,7 @@ pub mod memory; pub mod peripheral; pub mod semihosting; pub mod system; +pub mod gdb; use crate::core::instruction::instruction_size;