2a69f1bd2f
* flash/nor: Add support for TI CC26xx/CC13xx flash Added cc26xx flash driver to support the TI CC26xx and CC13xx microcontrollers. Driver is capable of determining which MCU is connected and configures itself accordingly. Added config files for four specific variants: CC26x0, CC13x0, CC26x2, and CC13x2. Note that the flash loader code is based on the sources used to support flash in Code Composer Studio and Uniflash from TI. Removed cc26xx.cfg file made obsolete by this patch. Change-Id: Ie2b0f74f8af7517a9184704b839677d1c9787862 Signed-off-by: Edward Fewell <efewell@ti.com> Reviewed-on: http://openocd.zylin.com/4358 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-by: Fredrik Hederstierna <fredrik@hederstierna.com> * flash/nor/nrf5: remove is_erased setting and autoerase before write Cached flash erase state in sectors[].is_erased is not reliable as running target can change the flash. Autoerase was issued before flash write on condition is_erased != 1 Remove autoerase completely as it is a quite non-standard feature. Change-Id: I19bef459e6afdc4c5fcaa2ccd194cf05be8a42b6 Signed-off-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-on: http://openocd.zylin.com/4400 Tested-by: jenkins * src/flash/tms470: remove testing of sectors[].is_erased state The erase check routine checked sectors only if is_erased != 1 Check sector unconditionally. While on it fix clang static analyzer warnings. Change-Id: I9988615fd8530c55a9b0c54b1900f89b550345e9 Signed-off-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-on: http://openocd.zylin.com/4401 Tested-by: jenkins * tcl/target/stm32f7x: configure faster system clock in reset-init STM32F7xx devices need faster clock for flash programming over JTAG transport. Using reset default 16 MHz clock resulted in lot of DAP WAITs and substantial decrease of flashing performance. Adapted to the restructured dap support (see2231da8ec4). Change-Id: Ida6915331dd924c9c0d08822fd94c04ad408cdc5 Signed-off-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-on: http://openocd.zylin.com/4464 Tested-by: jenkins Reviewed-by: Christopher Head <chead@zaber.com> * flash/nor/psoc5lp: fix compile issue on GCC 8.1.0 Issue already identified by Alex https://sourceforge.net/u/alexbour/ in ticket #191 https://sourceforge.net/p/openocd/tickets/191/ src/flash/nor/psoc5lp.c:237:2: error: ‘strncpy’ output truncated before terminating nul copying 2 bytes from a string of the same length [-Werror=stringop-truncation] Fix it by assigning the value to the array elements. Change-Id: I22468e5700efa64ea48ae8cdec930c48b4a7d8fb Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4563 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/arm: Add PLD command to ARM disassembler. Updates the ARM disassembler to handle PLD (PreLoad Data) commands. Previously handled by printing a TODO message. There are three forms of the command: literal, register, and immediate. Simply decode based off of the A1 encoding for the instructions in the ARM ARM. Also fixes mask to handle PLDW commands. Change-Id: I63bf97f16af254e838462c7cfac80f6c4681c556 Signed-off-by: James Marshall <jcmarsh@gwmail.gwu.edu> Reviewed-on: http://openocd.zylin.com/4348 Tested-by: jenkins Reviewed-by: Andreas Fritiofson <andreas.fritiofson@gmail.com> * mips_m4k.c: Fix build with --disable-target64 Replace PRIx64 with TARGET_PRIxADDR to avoid build problems when --disable-target64 is used during configure. Change-Id: I054a27a491e86c42c9386a0488194320b808ba96 Signed-off-by: Liviu Ionescu <ilg@livius.net> Reviewed-on: http://openocd.zylin.com/4566 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-by: Tim Newsome <tim@sifive.com> * target/arm_adi_v5: sync CSW and TAR cache on apreg write When using apreg to change AP registers CSW or TAR we get internal cached value not valid anymore. Reuse the setup functions for CSW and TAR to write them. Invalidate the cached value before the call to force the write, thus keeping original apreg behaviour. Change-Id: Ib14fafd5e584345de94f2e983de55406c588ac1c Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4565 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/arm_adi_v5: keep CSW and TAR cache updated The call to dap_queue_ap_write() can fail and the value in CSW and TAR becomes unknown. Invalidate the OpenOCD cache if dap_queue_ap_write() fails. Change-Id: Id6ec370b4c5ad07e454464780c1a1c8ae34ac870 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4564 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * tcl/target: Add Renesas R-Car R8A7794 E2 target Add configuration for the Renesas R-Car R8A7794 E2 target. This is an SoC with two Cortex A7 ARMv7a cores, both A7 cores are supported. Change-Id: Ic1c81840e3bfcef8ee1de5acedffae5c83612a5e Signed-off-by: Marek Vasut <marek.vasut@gmail.com> Reviewed-on: http://openocd.zylin.com/4531 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * tcl/board: Add Renesas R-Car R8A7790 H2 Stout board Add configuration for the Renesas R-Car R8A7790 H2 based Stout ADAS board. Change-Id: Ib880b5d2e1fab5c8c0bc0dbcedcdce8055463fe2 Signed-off-by: Marek Vasut <marek.vasut@gmail.com> Reviewed-on: http://openocd.zylin.com/4497 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * tcl/board: Add Renesas R-Car R8A7791 M2W Porter board Add configuration for the Renesas R-Car R8A7791 M2W based Porter evaluation board. Change-Id: Iaadb18f29748f890ebb68519ea9ddbd18e7649af Signed-off-by: Marek Vasut <marek.vasut@gmail.com> Reviewed-on: http://openocd.zylin.com/4498 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * tcl/board: Add Renesas R-Car R8A7794 E2 Silk board Add configuration for the Renesas R-Car R8A7794 E2 based Silk evaluation board. Change-Id: I504b5630b1a2791ed6967c6c2af8851ceef9723f Signed-off-by: Marek Vasut <marek.vasut@gmail.com> --- NOTE: This requires SW7[1] in position 1 (default is 0) Reviewed-on: http://openocd.zylin.com/4532 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * tcl/board: Factor out common R-Car Gen2 code Factor out the code shared by all R-Car Gen2 boards into a single file to get rid of the duplication. Change-Id: I70b302c2e71f4e6fdccb2817dd65a5493bb393d8 Signed-off-by: Marek Vasut <marek.vasut@gmail.com> Reviewed-on: http://openocd.zylin.com/4533 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * jtag/drivers/cmsis-dap: fix connect in cmsis_dap_swd_switch_seq() The proc cmsis_dap_swd_switch_seq() is part of the SWD API for this interface driver. It is valid only when the interface is used in SWD mode. In this proc there is the need to call, in sequence, first cmsis_dap_cmd_DAP_Disconnect() then cmsis_dap_cmd_DAP_Connect(). The latter call requires the connection mode as parameter, that inside cmsis_dap_swd_switch_seq() can only be CONNECT_SWD. The current implementation is not correct and in some cases can pass mode CONNECT_JTAG. Moreover, JTAG is optional in CMSIS-DAP and passing mode CONNECT_JTAG triggers an error with SWD-only interfaces. Use mode CONNECT_SWD in SWD specific cmsis_dap_swd_switch_seq(). Change-Id: Ib455bf5b69cb2a2d146a6c8875387b00c27a5690 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4571 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/cortex_m: return error if breakpoint address is out of range If the "Flash Patch and Breakpoint" unit is rev.1 then it can only accept breakpoint addresses below 0x1FFFFFFF. Detailed info in "ARM v7-M Architecture Reference Manual", DDI0403E at chapter "C1.11 Flash Patch and Breakpoint unit". Print a message and return error if the address of hardware breakpoint cannot be handled by the breakpoint unit. Change-Id: I95c92b1f058f0dfc568bf03015f99e439b27c59b Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4535 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-by: Christopher Head <chead@zaber.com> * flash/nor/stm32: Report errors in wait_status_busy Flash operation errors that occur during algorithm programming are reported via the algorithm return value. However, Flash operation errors that occur during non-algorithm work (erasing, programming without a work area, programming the last non-multiple-of-32-bytes on an H7, etc.) generally end with a call to stm32x_wait_status_busy, which reads the status register and clears the error flags but fails to actually report that something went wrong should an error flag (other than WRPERR) be set. Return an error status from stm32x_wait_status_busy in those cases. Correct a log message accordingly. Change-Id: I09369ea5f924fe58833aec1f45e52320ab4aaf43 Signed-off-by: Christopher Head <chead@zaber.com> Reviewed-on: http://openocd.zylin.com/4519 Tested-by: jenkins Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * flash/nor/stm32: Eliminate working area leak On a specific early-return path, an allocated working area was not freed. Free it. Change-Id: I7c8fe51ff475f191624086996be1c77251780b77 Signed-off-by: Christopher Head <chead@zaber.com> Reviewed-on: http://openocd.zylin.com/4520 Tested-by: jenkins Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * flash/nor/stm32h7: Fix incorrect comment The name of the bit according to the reference manual is inconsistency error, not increment error. Change-Id: Ie3b73c0312db586e35519e03fd1a5cb225673d97 Signed-off-by: Christopher Head <chead@zaber.com> Reviewed-on: http://openocd.zylin.com/4521 Tested-by: jenkins Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk> * target: fix 'bp' command help message "asid" and "length" are separate arguments of the command. Put space between them. Change-Id: I36cfc1e3a01caafef4fc3b26972a0cc192b0b963 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4511 Tested-by: jenkins Reviewed-by: Christopher Head <chead@zaber.com> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * Add ARM v8 AArch64 semihosting support This patch implements semihosting support for AArch64. This picks code from previously submitted AArch64 semihosting support patch and rebases on top of reworked semihosting code. Tested in AArch64 mode on a Lemaker Hikey Board with NewLib and GDB. Change-Id: I228a38f1de24f79e49ba99d8514d822a28c2950b Signed-off-by: Omair Javaid <omair.javaid@linaro.org> Reviewed-on: http://openocd.zylin.com/4537 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * GDB fileIO stdout support This patch fixes gdb fileio support to allow gdb console to be used as stdout. Now we can do something like gdb <inferior file> (gdb) tar ext :3333 (gdb) load (gdb) monitor arm semihosting enable (gdb) monitor arm semihosting_fileio enable (gdb) continue Here: Output from inferior using puts, printf etc will be routed to gdb console. Change-Id: I9cb0dddda1de58038c84f5b035c38229828cd744 Signed-off-by: Omair Javaid <omair.javaid@linaro.org> Reviewed-on: http://openocd.zylin.com/4538 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * target: armv8: Avoid semihosting segfault on halt Avoid a NULL pointer dereference when halting an aarch64 core. Change-Id: I333d40475ab26e2f0dca5c27302a5fa4d817a12f Signed-off-by: Andreas Färber <afaerber@suse.de> Reviewed-on: http://openocd.zylin.com/4593 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * tcl: target: Add NXP LS1012A config As seen on the FRDM-LS1012A board. Change-Id: Ifc9074b3f7535167b9ded5f544501ec2879f5db7 Signed-off-by: Andreas Färber <afaerber@suse.de> Reviewed-on: http://openocd.zylin.com/4594 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * tcl: board: Add NXP Freedom FRDM-LS1012A config An update for the K20 CMSIS-DAP firmware can be found here: https://community.nxp.com/thread/387080?commentID=840141#comment-840141 Change-Id: I149d7f8610aa56daf1aeb95f14ee1bf88f7cb647 Signed-off-by: Andreas Färber <afaerber@suse.de> Reviewed-on: http://openocd.zylin.com/4595 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * gdb_server: only trigger once the event gdb-detach at gdb quit When GDB quits (e.g. with "quit" command) we first execute gdb_detach() to reply "OK" then, at GDB disconnect (either TCP or pipe connection type), we execute gdb_connection_closed(). In case GDB is killed or it crashes, OpenOCD only executes the latter when detects the disconnection. Both gdb_detach() and gdb_connection_closed() trigger the event TARGET_EVENT_GDB_DETACH thus getting it triggered twice on clean GDB quit. Do not trigger the event TARGET_EVENT_GDB_DETACH in gdb_detach() and let only gdb_connection_closed() to handle it. Change-Id: Iacf035c855b8b3e2239c1c0e259c279688b418ee Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4585 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * gdb_server: set current_target from connection's one In a multi-target environment we are supposed to have a single gdb server for each target (or for each group of targets within a SMP node). By default, the gdb attached to a server sends its command to the target (or to the SMP node targets) linked to that server. This is working fine for the normal gdb commands, but it is broken for the native OpenOCD commands executed through gdb "monitor" command. In the latter case, gdb "monitor" commands will be executed on the current target of OpenOCD configuration script (that is either the last target created or the target specified in a "targets" command). Fixed in gdb_new_connection() by replacing the current target in the connection's copy of command context. Change-Id: If7c8f2dce4a3138f0907d3000dd0b15e670cfa80 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4586 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-by: Christopher Head <chead@zaber.com> * target/image: make i/j unsigned to avoid ubsan runtime error src/target/image.c:1055:15: runtime error: left shift of 128 by 24 places cannot be represented in type 'int' Change-Id: I322fd391cf3f242beffc8a274824763c8c5e69a4 Signed-off-by: Cody Schafer <openocd@codyps.com> Reviewed-on: http://openocd.zylin.com/4584 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-by: Christopher Head <chead@zaber.com> * target/stm32f7x: Clear stuck HSE clock with CSS Change-Id: Ica0025ea465910dd664ab546b66f4f25b271f1f5 Signed-off-by: Christopher Head <chead@zaber.com> Reviewed-on: http://openocd.zylin.com/4570 Tested-by: jenkins Reviewed-by: Andreas Fritiofson <andreas.fritiofson@gmail.com> * psoc5lp: fix erase check, add free_driver_priv psoc5lp_erase_check() was not properly adapted to the new armv7m_blank_check_memory() in the hot fix53376dbbedThis change fixes handling of num_sectors in dependecy of ecc_enabled. Also add comments how ecc_enabled influences num_sectors. Add pointer to default_flash_free_driver_priv() to all psoc5lp flash drivers to keep valgrind happy. Change-Id: Ie1806538becd364fe0efb7a414f0fe6a84b2055b Signed-off-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-on: http://openocd.zylin.com/4569 Tested-by: jenkins * target: atmel samd10 xplained mini cortex m0+ on a tiny board, with an mEDBG (CMSIS-DAP) debug interface. Change-Id: Iaedfab578b4eb4aa2d923bd80f220f59b34e6ef9 Signed-off-by: Karl Palsson <karlp@tweak.net.au> Reviewed-on: http://openocd.zylin.com/3402 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * tcl/board: add SAMD11 Xplained Pro evaluation board Change-Id: Id996c4de6dc9f25f71424017bf07689fea7bd3af Signed-off-by: Peter Lawrence <majbthrd@gmail.com> Reviewed-on: http://openocd.zylin.com/4507 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * Adds SAMD11D14AU flash support. Corrects names of SAMD11D14AM and SAMD11D14ASS per datasheet. Change-Id: I8beb15d5376966a4f8d7de76bfb2cbda2db440dc Signed-off-by: Christopher Hoover <ch@murgatroid.com> Reviewed-on: http://openocd.zylin.com/4597 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * nds32: Avoid detected JTAG clock AICE2 doesn't support scan for the maximum clock frequency of JTAG chain. It will cause USB command timeout. Change-Id: I41d1e3be387b6ed5a4dd0be663385a5f053fbcf9 Signed-off-by: Hellosun Wu <wujiheng.tw@gmail.com> Reviewed-on: http://openocd.zylin.com/4292 Tested-by: jenkins Reviewed-by: Hsiangkai Wang <hsiangkai@gmail.com> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * flash/nor/tcl: Distinguish between sectors and blocks in status messages Use the right word in flash protect command status messages based on whether the target bank defines num_prot_blocks. Minor message style tidy-up. Change-Id: I5f40fb5627422536ce737f242fbf80feafe7a1fc Signed-off-by: Dominik Peklo <dom.peklo@gmail.com> Reviewed-on: http://openocd.zylin.com/4573 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-by: Christopher Head <chead@zaber.com> * drivers: cmsis-dap: pull up common connect code Just a minor deduplication Change-Id: Idd256883e5f6d4bd4dcc18462dd5468991f507b3 Signed-off-by: Karl Palsson <karlp@tweak.net.au> Reviewed-on: http://openocd.zylin.com/3403 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * drivers: cmsis-dap: Print version info when available No need to wait until after connecting, might help diagnose part information by printing earlier. Change-Id: I51eb0d584be306baa811fbeb1ad6a604773e602c Signed-off-by: Karl Palsson <karlp@tweak.net.au> Reviewed-on: http://openocd.zylin.com/3404 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * flash/nor: add support for TI MSP432 devices Added msp432 flash driver to support the TI MSP432P4x and MSP432E4x microcontrollers. Implemented the flash algo helper as used in the TI debug and flash tools. This implemention supports the MSP432E4, Falcon, and Falcon 2M variants. The flash driver automatically detects the connected variant and configures itself appropriately. Added command to mass erase device for consistency with TI tools and added command to unlock the protected BSL region. Tested using MSP432E401Y, MSP432P401R, and MSP432P4111 LaunchPads. Tested with embedded XDS110 debug probe in CMSIS-DAP mode and with external SEGGER J-Link probe. Removed ti_msp432p4xx.cfg file made obsolete by this patch. Change-Id: I3b29d39ccc492524ef2c4a1733f7f9942c2684c0 Signed-off-by: Edward Fewell <efewell@ti.com> Reviewed-on: http://openocd.zylin.com/4153 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * flash/nor/at91sam4: fix sam4sa16c flash banks and its gpnvms count There was already a github fork that had this fixed, but as we try to use the latest, non-modified version of all software we use, I would like to have this fix in the next releases of OpenOCD so that if people uses $packagemanager, they will not have issues flashing the last part of the flash of sam4sa16c chips. Additionally, I've added some more logging related to the flash bank that was used, and the chip ID that was detected. Change-Id: I7ea5970105906e4560b727e46222ae9a91e41559 Signed-off-by: Erwin Oegema <blablaechthema@hotmail.com> Reviewed-on: http://openocd.zylin.com/4599 Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Tested-by: jenkins * flash/nor/stm32lx: Add revision 'V' for STM32L1xx Cat.3 devices Change-Id: Ic92b0fb5b738af3bec79ae335876aa9e26f5f4cd Signed-off-by: Marc Schink <openocd-dev@marcschink.de> Reviewed-on: http://openocd.zylin.com/4600 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com> * Avoid null target->semihosting references. The new common semihosting code introduced a bug, in certain conditions target->semihosting was used without semihosting being initialised. The solution was to explicitly test for target->semihosting before dereferencing it. Change-Id: I4c83e596140c68fe4ab32e586e51f7e981a40798 Signed-off-by: Liviu Ionescu <ilg@livius.net> Reviewed-on: http://openocd.zylin.com/4603 Tested-by: jenkins Reviewed-by: Jonathan Larmour <jifl@eCosCentric.com> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * nrf5: Add HWID 0x139 (52832 rev E0) Change-Id: I71b7471ccfcb8fcc6de30da57ce4165c7fb1f73f Signed-off-by: James Jacobsson <slowcoder@gmail.com> Reviewed-on: http://openocd.zylin.com/4604 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target: Fix segfault for 'mem2array' Call 'mem2array' without arguments to reproduce the segmentation fault. Change-Id: I02bf46cc8bd317abbb721a8c75d7cbfac99eb34e Signed-off-by: Marc Schink <openocd-dev@marcschink.de> Reviewed-on: http://openocd.zylin.com/4534 Tested-by: jenkins Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-by: Christopher Head <chead@zaber.com> * target/armv7m_trace: Fix typo in enum Change-Id: I6364ee5011ef2d55c59674e3b97504a285de0cb2 Signed-off-by: Marc Schink <openocd-dev@marcschink.de> Reviewed-on: http://openocd.zylin.com/3904 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * target/armv7m_trace: Use prefix for enums Change-Id: I3f199e6053146a1094d96b98ea174b41bb021599 Signed-off-by: Marc Schink <openocd-dev@marcschink.de> Reviewed-on: http://openocd.zylin.com/3905 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * target/aarch64: Call aarch64_init_debug_access() earlier in aarch64_deassert_reset() On Renesas R-Car, calling 'reset halt' and 'reset init' always made DAP inaccessible. Calling 'reset' and 'halt' seperatly worked fine. The only differences seems to be the point in time when aarch64_init_debug_access() is called. This patch aligns the behaviour. Change-Id: I2296c65e48414a7d9846f12a395e5eca315b49ca Signed-off-by: Dennis Ostermann <dennis.ostermann@renesas.com> Reviewed-on: http://openocd.zylin.com/4607 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * server: Improve signal handling under Linux Commit5087a955added custom signal handlers for the openocd server process. Before this commit, when openocd is run as a background process having the same controlling terminal as gdb, Control-C would be handled by gdb to stop target execution and return to the gdb prompt. However, after commit5087a955, the SIGINT caused by pressing Control-C also terminates openocd, effectively crashing the debugging session. The only way to avoid this is run openocd in a different controling terminal or to detach openocd from its controlling terminal, thus losing all job control for the openocd process. This patch improves the server's handling of POSIX signals: 1) Keyboard generated signals (INT and QUIT) are ignored when server process has is no controlling terminal. 2) SIGHUP and SIGPIPE are handled to ensure that .quit functions for each interface are called if user's logs out of X session or there is a network failure. SIG_INT & SIG_QUIT still stop openocd when it is running in the foreground. Change-Id: I03ad645e62408fdaf4edc49a3550b89b287eda10 Signed-off-by: Brent Roman <genosensor@gmail.com> Signed-off-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-on: http://openocd.zylin.com/3963 Tested-by: jenkins Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com> * armv7a: read ttbcr and ttb0/1 at every entry in debug state Commitbfc5c764dfavoids reading ttbcr and ttb0/1 at every virt2phys translation by caching them, and it updates the cached values in armv7a_arch_state(). But the purpose of any (*arch_state)() method, thus including armv7a_arch_state(), is to only print out and inform the user about some architecture specific status. Moreover, to reduce the verbosity during a GDB session, the method (*arch_state)() is not executed anymore at debug state entry (check use of target->verbose_halt_msg in src/openocd.c), thus the state of translation table gets out-of-sync triggering Error: Address translation failure or even using a wrong address in the memory R/W operation. In addition, the commit above breaks the case of armv7r by calling armv7a_read_ttbcr() unconditionally. Fixed by moving in cortex_a_post_debug_entry() the call to armv7a_read_ttbcr() on armv7a case only. Remove the call to armv7a_read_ttbcr() in armv7a_identify_cache() since it is (conditionally) called only in the same procedure cortex_a_post_debug_entry(). Fixes:bfc5c764df("armv7a: cache ttbcr and ttb0/1 on debug state entry") Change-Id: Ifc20eca190111832e339a01b7f85d28c1547c8ba Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4601 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * Avoid dereferencing NULL pointer. If a NULL pointer is passed, don't attempt to increment it. This avoids passing the now not-NULL pointer on and eventually segfaulting. Also remove some unnecessary temporary variables. Change-Id: I268e225121aa283d59179bfae407ebf6959d3a4e Signed-off-by: Darius Rad <darius@bluespec.com> Reviewed-on: http://openocd.zylin.com/4550 Tested-by: jenkins Reviewed-by: Andreas Fritiofson <andreas.fritiofson@gmail.com> * Remove FSF mailing address. Checkpatch complains about this (FSF_MAILING_ADDRESS). Change-Id: Ib46a7704f9aed4ed16ce7733d43c58254a094149 Signed-off-by: Tim Newsome <tim@sifive.com> Reviewed-on: http://openocd.zylin.com/4559 Tested-by: jenkins Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk> * drivers: cmsis_dap_usb: implement cmd JTAG_TMS Simply add a wrapper around cmsis_dap_cmd_DAP_SWJ_Sequence() Change-Id: Icf86f84b24e9fec56e2f9e155396aac34b0e06d2 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4517 Tested-by: jenkins Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk> * arm_adi_v5: put SWJ-DP back to JTAG mode at exit When SWD mode is used, current OpenOCD code left the SWJ-DP in SWD mode at exit. Also, current code is unable to switch back the SWJ-DP in JTAG at next run, thus a power cycle of both target and interface is required in order to run OpenOCD in JTAG mode again. Put the SWJ-DP back to JTAG mode before exit from OpenOCD. Use switch_seq(SWD_TO_JTAG) instead of dap_to_jtag(), because the latter is not implemented on some interfaces. This is aligned with the use of switch_seq(JTAG_TO_SWD) in swd_connect(). Change-Id: I55d3faebe60d6402037ec39dd9700dc5f17c53b0 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4493 Tested-by: jenkins Reviewed-by: Bohdan Tymkiv <bhdt@cypress.com> Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * Add RISC-V support. This supports both 0.11 and 0.13 versions of the debug spec. Support for `-rtos riscv` will come in a separate commit since it was easy to separate out, and is likely to be more controversial. Flash support for the SiFive boards will also come in a later commit. Change-Id: I1d38fe669c2041b4e21a5c54a091594aac3e2190 Signed-off-by: Tim Newsome <tim@sifive.com> Reviewed-on: http://openocd.zylin.com/4578 Tested-by: jenkins Reviewed-by: Liviu Ionescu <ilg@livius.net> Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * usb_blaster: Don't unnecessarily go through DR-/IR-Pause There is no need to pass through DR-/IR-Pause after a scan if we want to go to DR-/IR-Update. We just have to skip the first step of the path to the end state because we already did that step when shifting the last bit. v2: - Fix comments as remarked in review of v1 Change-Id: I3c10f02794b2233f63d2150934e2768430873caa Signed-off-by: Daniel Glöckner <daniel-gl@gmx.net> Reviewed-on: http://openocd.zylin.com/4245 Tested-by: jenkins Reviewed-by: Christopher Head <chead@zaber.com> Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * cortex_a: fix virt2phys when mmu is disabled When the MMU is not enabled on debug state entry, virt2phys cannot perform a translation since it is unknown whether a valid MMU configuration existed before. In this case, return the virtual address as physical address. Change-Id: I6f85a7a5dbc200be1a4b5badf10a1a717f1c79c0 Signed-off-by: Matthias Welwarsky <matthias.welwarsky@sysgo.com> Reviewed-on: http://openocd.zylin.com/4480 Tested-by: jenkins Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * drivers: cmsis-dap: print serial if available Helpful for sanity checking connections Change-Id: Ife0d8b4e12d4c03685aac8115c9739a4c1e994fe Signed-off-by: Karl Palsson <karlp@tweak.net.au> Reviewed-on: http://openocd.zylin.com/3405 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/cortex_m: make a variable local The vec_ids variable is not referenced anywhere other than the vector catch command handler. Make it local to that function. Change-Id: Ie5865e8f78698c19a09f0b9d58269ced1c9db440 Signed-off-by: Christopher Head <chead@zaber.com> Reviewed-on: http://openocd.zylin.com/4606 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/cortex_a: fix compile error for uninitialized variable Commitad6c71e151introduced the variable "mmu_enabled" whose pointer is passed to cortex_a_mmu() that initialises it. This initialization is not visible to the compiler that issue a compile error. The same situation is common across the same file and the usual workaround is to initialize it to zero; thus the same fix i applied here. Ticket: https://sourceforge.net/p/openocd/tickets/197/ Fixes: commitad6c71e151("cortex_a: fix virt2phys when mmu is disabled") Change-Id: I77dec41acdf4c715b45ae37b72e36719d96d9283 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4619 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * mips_m4k: add optional reset handler In some cases by using SRST we can't halt CPU early enough. And option PrRst is not available too. In this case the only way is to set BOOT flag over EJTAG and reset CPU or SoC from CPU itself. For example by writing to some reset register. This patch is providing possibility to use user defined reset-assert handler which will be enabled only in case SRST is disabled. It is needed to be able switch between two different reset variants on run time. Change-Id: I6ef98f1871ea657115877190f7cc7a5e8f3233e4 Signed-off-by: Oleksij Rempel <linux@rempel-privat.de> Reviewed-on: http://openocd.zylin.com/4404 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * tcl/target: add config for Qualcomm QCA4531 The QCA4531 is a two stream (2x2) 802.11b/g/n single-band programmable Wi-Fi System-on-Chip (SoC) for the Internet of Things (IoT). https://www.qualcomm.com/products/qca4531 Change-Id: I58398c00943b005cfaf0ac1eaad92d1fa4e2cba7 Signed-off-by: Oleksij Rempel <linux@rempel-privat.de> Reviewed-on: http://openocd.zylin.com/4405 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * tcl/board: add config for 8devices LIMA board More information about this board can be found here: https://www.8devices.com/products/lima Change-Id: Id35a35d3e986630d58d37b47828870afd107cc6a Signed-off-by: Oleksij Rempel <linux@rempel-privat.de> Reviewed-on: http://openocd.zylin.com/4406 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * tcl/target|board: move common AR9331 code to atheros_ar9331.cfg The ar9331_25mhz_pll_init and ar9331_ddr1_init routines can be used not only for TP-Link MR3020 board, so move them to the common atheros_ar9331.cfg file. Change-Id: I04090856b08151d6bb0f5ef9cc654efae1c81835 Signed-off-by: Antony Pavlov <antonynpavlov@gmail.com> Reviewed-on: http://openocd.zylin.com/2999 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * tcl/target/atheros_ar9331: add DDR2 helper this helper works on many different boards, so it is good to have it in target config Change-Id: I068deac36fdd73dbbcedffc87865cc5b9d992c1d Signed-off-by: Oleksij Rempel <linux@rempel-privat.de> Reviewed-on: http://openocd.zylin.com/4422 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * tcl/target/atheros_ar9331: add documentation and extra helpers Sync it with experience gathered on Qualcomm QCA4531 SoC. This chips are in many ways similar. Change-Id: I06b9c85e5985a09a9be3cb6cc0ce3b37695d2e54 Signed-off-by: Oleksij Rempel <linux@rempel-privat.de> Reviewed-on: http://openocd.zylin.com/4423 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * tcl/board: add DPTechnics DPT-Board-v1 it is Atheros AR9331 based IoT dev board. Change-Id: I6fc3cdea1bef49c53045018ff5acfec4d5610ba6 Signed-off-by: Oleksij Rempel <linux@rempel-privat.de> Reviewed-on: http://openocd.zylin.com/4424 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * fpga/altera-10m50: add all device id add all currently know Intel (Alter) MAX 10 device ids Change-Id: I6a88fef222c8e206812499d41be863c3d89fa944 Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de> Reviewed-on: http://openocd.zylin.com/4598 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * target|board: Add Intel (Altera) Arria 10 target and related board Target information about this SoC can be found here: https://www.altera.com/products/fpga/arria-series/arria-10/overview.html Achilles Instant-Development Kit Arria 10 SoC SoM: https://www.reflexces.com/products-solutions/development-kits/arria-10/achilles-instant-development-kit-arria-10-soc-som Change-Id: Id78c741be6a8b7d3a70f37d41088e47ee61b437a Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de> Reviewed-on: http://openocd.zylin.com/4583 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * target/riscv: fix compile error with gcc 8.1.1 Fix compile error: src/target/riscv/riscv-011.c: In function ‘slot_offset’: src/target/riscv/riscv-011.c:238:4: error: this statement may fall through [-Werror=implicit-fallthrough=] switch (slot) { ^~~~~~ src/target/riscv/riscv-011.c:243:3: note: here case 64: ^~~~ Fixes:a51ab8ddf6("Add RISC-V support.") Change-Id: I7fa86b305bd90cc590fd4359c3698632d44712e5 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4618 Tested-by: jenkins Reviewed-by: Jiri Kastner <cz172638@gmail.com> Reviewed-by: Oleksij Rempel <linux@rempel-privat.de> Reviewed-by: Tim Newsome <tim@sifive.com> Reviewed-by: Paul Fertser <fercerpav@gmail.com> * server: explicitly call "shutdown" when catch CTRL-C or a signal Every TCL command can be renamed (or deleted) and then replaced by a TCL proc that has the same name of the original TCL command. This can be used either to completely replace an existing command or to wrap the original command to extend its functionality. This applies also to the OpenOCD command "shutdown" and can be useful, for example, to set back some default value to the target before quitting OpenOCD. E.g. (TCL code): rename shutdown original_shutdown proc shutdown {} { puts "This is my implementation of shutdown" # my own stuff before exit OpenOCD original_shutdown } Unfortunately, sending a signal (or pressing CTRL-C) to terminate OpenOCD doesn't trigger calling the original "shutdown" command nor its (eventual) replacement. Detect if the main loop is terminated by an external signal and in such case execute explicitly the command "shutdown". Replace with enum the magic numbers assumed by "shutdown_openocd". Please notice that it's possible to write a custom "shutdown" TCL proc that does not call the original "shutdown" command. This is useful, for example, to prevent the user to quit OpenOCD by typing "shutdown" in the telnet session. Such case will not prevent OpenOCD to terminate when receiving a signal; OpenOCD will quit after executing the custom "shutdown" command. Change-Id: I86b8f9eab8dbd7a28dad58b8cafd97caa7a82f43 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4551 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * zy1000: fix compile error with gcc 8.1.1 The fall-through comment is not taken in consideration by gcc 8.1.1 because it is inside the braces of a C-code block. Move the comment outside the C block. Change-Id: I22d87b2dee109fb8bcf2071ac55fdf7171ffcf4b Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4614 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * flash/nor/tcl.c: fix flash bank bounds check in 'flash fill' command handler Steps to reproduce ( STM32F103 'Blue Pill', 128KiB of flash ): > flash fillh 0x0801FFFE 00 1 wrote 2 bytes to 0x0801fffe in 0.019088s (0.102 KiB/s) > flash fillw 0x0801FFFE 00 1 Error: stm32f1x.cpu -- clearing lockup after double fault Error: error waiting for target flash write algorithm Error: error writing to flash at address 0x08000000 at offset 0x0001fffe Change-Id: I145092ec5e45bc586b3df48bf37c38c9226915c1 Signed-off-by: Bohdan Tymkiv <bhdt@cypress.com> Reviewed-on: http://openocd.zylin.com/4516 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/arm_adi_v5: add command "dpreg" For very low level debug or development around DAP, it is useful to have direct access to DP registers. Add command "dpreg" by mimic the syntax of the existing "apreg" command: $dap_name dpreg reg [value] Change-Id: Ic4ab451eb5e74453133adee61050b4c6f656ffa3 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4612 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * nrf5: add free_driver_priv Change-Id: I429a9868deb0c4b51f47a4bbad844bdc348e8d21 Signed-off-by: Jim Paris <jim@jtan.com> Reviewed-on: http://openocd.zylin.com/4608 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * rtos: add support for NuttX This patch introduces RTOS support for NuttX. Currently, only ARM Cortex-M (both FPU and FPU-less) targets are supported. To use, add the following lines to ~/.gdbinit. define hookpost-file eval "monitor nuttx.pid_offset %d", &((struct tcb_s *)(0))->pid eval "monitor nuttx.xcpreg_offset %d", &((struct tcb_s *)(0))->xcp.regs eval "monitor nuttx.state_offset %d", &((struct tcb_s *)(0))->task_state eval "monitor nuttx.name_offset %d", &((struct tcb_s *)(0))->name eval "monitor nuttx.name_size %d", sizeof(((struct tcb_s *)(0))->name) end And please make sure the above values are the same as in src/rtos/nuttx_header.h Change-Id: I2aaf8644d24dfb84b500516a9685382d5d8fe48f Signed-off-by: Masayuki Ishikawa <Masayuki.Ishikawa@jp.sony.com> Signed-off-by: Masatoshi Tateishi <Masatoshi.Tateishi@jp.sony.com> Signed-off-by: Nobuto Kobayashi <Nobuto.Kobayashi@sony.com> Reviewed-on: http://openocd.zylin.com/4103 Tested-by: jenkins Reviewed-by: Alan Carvalho de Assis <acassis@gmail.com> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * server/server: Add ability to remove services Add the ability to remove services while OpenOCD is running. Change-Id: I4067916fda6d03485463fa40901b40484d94e24e Signed-off-by: Marc Schink <openocd-dev@marcschink.de> Reviewed-on: http://openocd.zylin.com/4054 Tested-by: jenkins Reviewed-by: Fredrik Hederstierna <fredrik@hederstierna.com> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/cortex_m: fix incorrect comment The code sets C_MASKINTS if that bit is not already set (correctly). Fix the comment to agree. Change-Id: If4543e2660a9fa2cdabb2d2698427a6c8d9a274c Signed-off-by: Christopher Head <chead@zaber.com> Reviewed-on: http://openocd.zylin.com/4620 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * tcl/target/stm32f0x: Allow overriding the Flash bank size Copy & paste from another stm32 target. Change-Id: I0f6cbcec974ce70c23c1850526354106caee1172 Signed-off-by: Dominik Peklo <dom.peklo@gmail.com> Reviewed-on: http://openocd.zylin.com/4575 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * tcl/target: add Allwinner V3s SoC support Change-Id: I2459d2b137050985b7301047f9651951d72d9e9e Signed-off-by: Oleksij Rempel <linux@rempel-privat.de> Reviewed-on: http://openocd.zylin.com/4427 Tested-by: jenkins Reviewed-by: Paul Fertser <fercerpav@gmail.com> * target/arm_adi_v5: allow commands apsel and apcsw during init phase The current implementation of apsel cannot be executed during the initialization phase because it queries the DAP AP to retrieve and print the content of IDR register, and the query is only possible later on during the exec phase. But IDR information is already printed by the dedicated command apid, making redundant printing it by apsel too. Being unable to run apsel during initialization, makes also apcsw command (that depends on apsel) not usable in such phase. Modify the command apsel to only set the current AP, without making any transfer to the (possibly not initialized yet) DAP. When run without parameters, just print the current AP number. Change mode to COMMAND_ANY to apsel and to apcsw. Change-Id: Ibea6d531e435d1d49d782de1ed8ee6846e91bfdf Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4624 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/cortex_a: allow command dacrfixup during init phase There is no reason to restrict the command "cortex_a dacrfixup" to the EXEC phase only. Change the command mode to ANY so the command can be used in the initialization phase too. Change-Id: I498cc6b2dbdc48b3b2dd5f0445519a51857b295f Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4623 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * target/armv7a_cache: add gdb keep-alive and fix a missing dpm finish Depending on range size, the loop on cache operations can take quite some time, causing gdb to timeout. Add keep-alive to prevent gdb to timeout. Add also a missing dpm->finish() to balance dpm->prepare(). Change-Id: Ia87934b1ec19a0332bb50e3010b582381e5f3685 Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-on: http://openocd.zylin.com/4627 Tested-by: jenkins Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de> * Add detail to `wrong register size` error. Signed-off-by: Tim Newsome <tim@sifive.com> Change-Id: Id31499c94b539969970251145e42c89c943fd87c Reviewed-on: http://openocd.zylin.com/4577 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * doc: fix typo in cortex_m maskisr command Change-Id: I37795c320ff7cbf6f2c7434e03b26dbaf6fc6db4 Signed-off-by: Christopher Head <chead@zaber.com> Reviewed-on: http://openocd.zylin.com/4621 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/cortex_m: restore C_MASKINTS after reset The cortex_m maskisr user-facing setting is not changed across a target reset. However, the in-core C_MASKINTS bit was always cleared as part of reset processing, meaning that a cortex_m maskisr on setting would not be respected after a reset. Set C_MASKINTS based on the user-facing setting value rather than always clearing it after reset. Change-Id: I5aa5b9dfde04a0fb9c6816fa55b5ef1faf39f8de Signed-off-by: Christopher Head <chead@zaber.com> Reviewed-on: http://openocd.zylin.com/4605 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * tcl/board: update all uses of interface/stlink-v2-1 to interface/stlink Change-Id: I5e27e84d022f73101376e8b4a1bdc65f58fd348a Signed-off-by: Cody P Schafer <openocd@codyps.com> Reviewed-on: http://openocd.zylin.com/4456 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz> * target/riscv/riscv-011: fix compile warning about uninitialized variable In MSYS2 MinGW 64-bit git clone git://git.code.sf.net/p/openocd/code openocd $ gcc --version gcc.exe (Rev1, Built by MSYS2 project) 8.2.0 ./bootstrap ./configure --prefix= $ cat config.status | grep CFLAGS CFLAGS='-g -O2' make bindir = "bin-x64" depbase=`echo src/target/riscv/riscv-011.lo | sed 's|[^/]*$|.deps/&|;s|\.lo$||'`;\ /bin/sh ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -D__USE_MINGW_ANSI_STDIO -I./src -I./src -I./src/helper -DPKGDATADIR=\"/mingw64/share/openocd\" -DBINDIR=\"bin-x64\" -I./jimtcl -I./jimtcl -Wall -Wstrict-prototypes -Wformat-security -Wshadow -Wextra -Wno-unused-parameter -Wbad-function-cast -Wcast-align -Wredundant-decls -Werror -g -O2 -MT src/target/riscv/riscv-011.lo -MD -MP -MF $depbase.Tpo -c -o src/target/riscv/riscv-011.lo src/target/riscv/riscv-011.c &&\ mv -f $depbase.Tpo $depbase.Plo libtool: compile: gcc -DHAVE_CONFIG_H -I. -D__USE_MINGW_ANSI_STDIO -I./src -I./src -I./src/helper -DPKGDATADIR=\"/mingw64/share/openocd\" -DBINDIR=\"bin-x64\" -I./jimtcl -I./jimtcl -Wall -Wstrict-prototypes -Wformat-security -Wshadow -Wextra -Wno-unused-parameter -Wbad-function-cast -Wcast-align -Wredundant-decls -Werror -g -O2 -MT src/target/riscv/riscv-011.lo -MD -MP -MF src/target/riscv/.deps/riscv-011.Tpo -c src/target/riscv/riscv-011.c -o src/target/riscv/riscv-011.o src/target/riscv/riscv-011.c: In function 'poll_target': src/target/riscv/riscv-011.c:1799:6: error: 'reg' may be used uninitialized in this function [-Werror=maybe-uninitialized] reg_cache_set(target, reg, ((data & 0xffffffff) << 32) | value); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/target/riscv/riscv-011.c:1686:17: note: 'reg' was declared here unsigned int reg; ^~~ cc1.exe: all warnings being treated as errors make[2]: *** [Makefile:3250: src/target/riscv/riscv-011.lo] Error 1 Change-Id: I6996dcb866fbace26817636f4bedba09510a087f Signed-off-by: Svetoslav Enchev <svetoslav.enchev@gmail.com> Reviewed-on: http://openocd.zylin.com/4635 Tested-by: jenkins Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com> Reviewed-by: Tim Newsome <tim@sifive.com> Reviewed-by: Tomas Vanek <vanekt@fbl.cz>
3693 lines
102 KiB
C
3693 lines
102 KiB
C
/***************************************************************************
|
|
* Copyright (C) 2005 by Dominic Rath *
|
|
* Dominic.Rath@gmx.de *
|
|
* *
|
|
* Copyright (C) 2007-2010 Øyvind Harboe *
|
|
* oyvind.harboe@zylin.com *
|
|
* *
|
|
* Copyright (C) 2008 by Spencer Oliver *
|
|
* spen@spen-soft.co.uk *
|
|
* *
|
|
* Copyright (C) 2011 by Broadcom Corporation *
|
|
* Evan Hunter - ehunter@broadcom.com *
|
|
* *
|
|
* Copyright (C) ST-Ericsson SA 2011 *
|
|
* michel.jaouen@stericsson.com : smp minimum support *
|
|
* *
|
|
* Copyright (C) 2013 Andes Technology *
|
|
* Hsiangkai Wang <hkwang@andestech.com> *
|
|
* *
|
|
* Copyright (C) 2013 Franck Jullien *
|
|
* elec4fun@gmail.com *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
***************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <target/breakpoints.h>
|
|
#include <target/target_request.h>
|
|
#include <target/register.h>
|
|
#include <target/target.h>
|
|
#include <target/target_type.h>
|
|
#include "server.h"
|
|
#include <flash/nor/core.h>
|
|
#include "gdb_server.h"
|
|
#include <target/image.h>
|
|
#include <jtag/jtag.h>
|
|
#include "rtos/rtos.h"
|
|
#include "target/smp.h"
|
|
|
|
/**
|
|
* @file
|
|
* GDB server implementation.
|
|
*
|
|
* This implements the GDB Remote Serial Protocol, over TCP connections,
|
|
* giving GDB access to the JTAG or other hardware debugging facilities
|
|
* found in most modern embedded processors.
|
|
*/
|
|
|
|
struct target_desc_format {
|
|
char *tdesc;
|
|
uint32_t tdesc_length;
|
|
};
|
|
|
|
/* private connection data for GDB */
|
|
struct gdb_connection {
|
|
char buffer[GDB_BUFFER_SIZE];
|
|
char *buf_p;
|
|
int buf_cnt;
|
|
int ctrl_c;
|
|
enum target_state frontend_state;
|
|
struct image *vflash_image;
|
|
bool closed;
|
|
bool busy;
|
|
int noack_mode;
|
|
/* set flag to true if you want the next stepi to return immediately.
|
|
* allowing GDB to pick up a fresh set of register values from the target
|
|
* without modifying the target state. */
|
|
bool sync;
|
|
/* We delay reporting memory write errors until next step/continue or memory
|
|
* write. This improves performance of gdb load significantly as the GDB packet
|
|
* can be replied immediately and a new GDB packet will be ready without delay
|
|
* (ca. 10% or so...). */
|
|
bool mem_write_error;
|
|
/* with extended-remote it seems we need to better emulate attach/detach.
|
|
* what this means is we reply with a W stop reply after a kill packet,
|
|
* normally we reply with a S reply via gdb_last_signal_packet.
|
|
* as a side note this behaviour only effects gdb > 6.8 */
|
|
bool attached;
|
|
/* temporarily used for target description support */
|
|
struct target_desc_format target_desc;
|
|
/* temporarily used for thread list support */
|
|
char *thread_list;
|
|
};
|
|
|
|
#if 0
|
|
#define _DEBUG_GDB_IO_
|
|
#endif
|
|
|
|
static struct gdb_connection *current_gdb_connection;
|
|
|
|
static int gdb_breakpoint_override;
|
|
static enum breakpoint_type gdb_breakpoint_override_type;
|
|
|
|
static int gdb_error(struct connection *connection, int retval);
|
|
static char *gdb_port;
|
|
static char *gdb_port_next;
|
|
|
|
static void gdb_log_callback(void *priv, const char *file, unsigned line,
|
|
const char *function, const char *string);
|
|
|
|
static void gdb_sig_halted(struct connection *connection);
|
|
|
|
/* number of gdb connections, mainly to suppress gdb related debugging spam
|
|
* in helper/log.c when no gdb connections are actually active */
|
|
int gdb_actual_connections;
|
|
|
|
/* set if we are sending a memory map to gdb
|
|
* via qXfer:memory-map:read packet */
|
|
/* enabled by default*/
|
|
static int gdb_use_memory_map = 1;
|
|
/* enabled by default*/
|
|
static int gdb_flash_program = 1;
|
|
|
|
/* if set, data aborts cause an error to be reported in memory read packets
|
|
* see the code in gdb_read_memory_packet() for further explanations.
|
|
* Disabled by default.
|
|
*/
|
|
static int gdb_report_data_abort;
|
|
/* If set, errors when accessing registers are reported to gdb. Disabled by
|
|
* default. */
|
|
static int gdb_report_register_access_error;
|
|
|
|
/* set if we are sending target descriptions to gdb
|
|
* via qXfer:features:read packet */
|
|
/* enabled by default */
|
|
static int gdb_use_target_description = 1;
|
|
|
|
/* current processing free-run type, used by file-I/O */
|
|
static char gdb_running_type;
|
|
|
|
static int gdb_last_signal(struct target *target)
|
|
{
|
|
switch (target->debug_reason) {
|
|
case DBG_REASON_DBGRQ:
|
|
return 0x2; /* SIGINT */
|
|
case DBG_REASON_BREAKPOINT:
|
|
case DBG_REASON_WATCHPOINT:
|
|
case DBG_REASON_WPTANDBKPT:
|
|
return 0x05; /* SIGTRAP */
|
|
case DBG_REASON_SINGLESTEP:
|
|
return 0x05; /* SIGTRAP */
|
|
case DBG_REASON_NOTHALTED:
|
|
return 0x0; /* no signal... shouldn't happen */
|
|
default:
|
|
LOG_USER("undefined debug reason %d - target needs reset",
|
|
target->debug_reason);
|
|
return 0x0;
|
|
}
|
|
}
|
|
|
|
static int check_pending(struct connection *connection,
|
|
int timeout_s, int *got_data)
|
|
{
|
|
/* a non-blocking socket will block if there is 0 bytes available on the socket,
|
|
* but return with as many bytes as are available immediately
|
|
*/
|
|
struct timeval tv;
|
|
fd_set read_fds;
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
int t;
|
|
if (got_data == NULL)
|
|
got_data = &t;
|
|
*got_data = 0;
|
|
|
|
if (gdb_con->buf_cnt > 0) {
|
|
*got_data = 1;
|
|
return ERROR_OK;
|
|
}
|
|
|
|
FD_ZERO(&read_fds);
|
|
FD_SET(connection->fd, &read_fds);
|
|
|
|
tv.tv_sec = timeout_s;
|
|
tv.tv_usec = 0;
|
|
if (socket_select(connection->fd + 1, &read_fds, NULL, NULL, &tv) == 0) {
|
|
/* This can typically be because a "monitor" command took too long
|
|
* before printing any progress messages
|
|
*/
|
|
if (timeout_s > 0)
|
|
return ERROR_GDB_TIMEOUT;
|
|
else
|
|
return ERROR_OK;
|
|
}
|
|
*got_data = FD_ISSET(connection->fd, &read_fds) != 0;
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_get_char_inner(struct connection *connection, int *next_char)
|
|
{
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
int retval = ERROR_OK;
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
char *debug_buffer;
|
|
#endif
|
|
for (;; ) {
|
|
if (connection->service->type != CONNECTION_TCP)
|
|
gdb_con->buf_cnt = read(connection->fd, gdb_con->buffer, GDB_BUFFER_SIZE);
|
|
else {
|
|
retval = check_pending(connection, 1, NULL);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
gdb_con->buf_cnt = read_socket(connection->fd,
|
|
gdb_con->buffer,
|
|
GDB_BUFFER_SIZE);
|
|
}
|
|
|
|
if (gdb_con->buf_cnt > 0)
|
|
break;
|
|
if (gdb_con->buf_cnt == 0) {
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
errno = WSAGetLastError();
|
|
|
|
switch (errno) {
|
|
case WSAEWOULDBLOCK:
|
|
usleep(1000);
|
|
break;
|
|
case WSAECONNABORTED:
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
case WSAECONNRESET:
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
default:
|
|
LOG_ERROR("read: %d", errno);
|
|
exit(-1);
|
|
}
|
|
#else
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
usleep(1000);
|
|
break;
|
|
case ECONNABORTED:
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
case ECONNRESET:
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
default:
|
|
LOG_ERROR("read: %s", strerror(errno));
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
debug_buffer = strndup(gdb_con->buffer, gdb_con->buf_cnt);
|
|
LOG_DEBUG("received '%s'", debug_buffer);
|
|
free(debug_buffer);
|
|
#endif
|
|
|
|
gdb_con->buf_p = gdb_con->buffer;
|
|
gdb_con->buf_cnt--;
|
|
*next_char = *(gdb_con->buf_p++);
|
|
if (gdb_con->buf_cnt > 0)
|
|
connection->input_pending = 1;
|
|
else
|
|
connection->input_pending = 0;
|
|
#ifdef _DEBUG_GDB_IO_
|
|
LOG_DEBUG("returned char '%c' (0x%2.2x)", *next_char, *next_char);
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* The cool thing about this fn is that it allows buf_p and buf_cnt to be
|
|
* held in registers in the inner loop.
|
|
*
|
|
* For small caches and embedded systems this is important!
|
|
*/
|
|
static inline int gdb_get_char_fast(struct connection *connection,
|
|
int *next_char, char **buf_p, int *buf_cnt)
|
|
{
|
|
int retval = ERROR_OK;
|
|
|
|
if ((*buf_cnt)-- > 0) {
|
|
*next_char = **buf_p;
|
|
(*buf_p)++;
|
|
if (*buf_cnt > 0)
|
|
connection->input_pending = 1;
|
|
else
|
|
connection->input_pending = 0;
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
LOG_DEBUG("returned char '%c' (0x%2.2x)", *next_char, *next_char);
|
|
#endif
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
gdb_con->buf_p = *buf_p;
|
|
gdb_con->buf_cnt = *buf_cnt;
|
|
retval = gdb_get_char_inner(connection, next_char);
|
|
*buf_p = gdb_con->buf_p;
|
|
*buf_cnt = gdb_con->buf_cnt;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_get_char(struct connection *connection, int *next_char)
|
|
{
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
return gdb_get_char_fast(connection, next_char, &gdb_con->buf_p, &gdb_con->buf_cnt);
|
|
}
|
|
|
|
static int gdb_putback_char(struct connection *connection, int last_char)
|
|
{
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
|
|
if (gdb_con->buf_p > gdb_con->buffer) {
|
|
*(--gdb_con->buf_p) = last_char;
|
|
gdb_con->buf_cnt++;
|
|
} else
|
|
LOG_ERROR("BUG: couldn't put character back");
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* The only way we can detect that the socket is closed is the first time
|
|
* we write to it, we will fail. Subsequent write operations will
|
|
* succeed. Shudder! */
|
|
static int gdb_write(struct connection *connection, void *data, int len)
|
|
{
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
if (gdb_con->closed)
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
|
|
if (connection_write(connection, data, len) == len)
|
|
return ERROR_OK;
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
static int gdb_put_packet_inner(struct connection *connection,
|
|
char *buffer, int len)
|
|
{
|
|
int i;
|
|
unsigned char my_checksum = 0;
|
|
#ifdef _DEBUG_GDB_IO_
|
|
char *debug_buffer;
|
|
#endif
|
|
int reply;
|
|
int retval;
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
|
|
for (i = 0; i < len; i++)
|
|
my_checksum += buffer[i];
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
/*
|
|
* At this point we should have nothing in the input queue from GDB,
|
|
* however sometimes '-' is sent even though we've already received
|
|
* an ACK (+) for everything we've sent off.
|
|
*/
|
|
int gotdata;
|
|
for (;; ) {
|
|
retval = check_pending(connection, 0, &gotdata);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
if (!gotdata)
|
|
break;
|
|
retval = gdb_get_char(connection, &reply);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
if (reply == '$') {
|
|
/* fix a problem with some IAR tools */
|
|
gdb_putback_char(connection, reply);
|
|
LOG_DEBUG("Unexpected start of new packet");
|
|
break;
|
|
}
|
|
|
|
LOG_WARNING("Discard unexpected char %c", reply);
|
|
}
|
|
#endif
|
|
|
|
while (1) {
|
|
#ifdef _DEBUG_GDB_IO_
|
|
debug_buffer = strndup(buffer, len);
|
|
LOG_DEBUG("sending packet '$%s#%2.2x'", debug_buffer, my_checksum);
|
|
free(debug_buffer);
|
|
#endif
|
|
|
|
char local_buffer[1024];
|
|
local_buffer[0] = '$';
|
|
if ((size_t)len + 4 <= sizeof(local_buffer)) {
|
|
/* performance gain on smaller packets by only a single call to gdb_write() */
|
|
memcpy(local_buffer + 1, buffer, len++);
|
|
len += snprintf(local_buffer + len, sizeof(local_buffer) - len, "#%02x", my_checksum);
|
|
retval = gdb_write(connection, local_buffer, len);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
} else {
|
|
/* larger packets are transmitted directly from caller supplied buffer
|
|
* by several calls to gdb_write() to avoid dynamic allocation */
|
|
snprintf(local_buffer + 1, sizeof(local_buffer) - 1, "#%02x", my_checksum);
|
|
retval = gdb_write(connection, local_buffer, 1);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
retval = gdb_write(connection, buffer, len);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
retval = gdb_write(connection, local_buffer + 1, 3);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
}
|
|
|
|
if (gdb_con->noack_mode)
|
|
break;
|
|
|
|
retval = gdb_get_char(connection, &reply);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
|
|
if (reply == '+')
|
|
break;
|
|
else if (reply == '-') {
|
|
/* Stop sending output packets for now */
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
LOG_WARNING("negative reply, retrying");
|
|
} else if (reply == 0x3) {
|
|
gdb_con->ctrl_c = 1;
|
|
retval = gdb_get_char(connection, &reply);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
if (reply == '+')
|
|
break;
|
|
else if (reply == '-') {
|
|
/* Stop sending output packets for now */
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
LOG_WARNING("negative reply, retrying");
|
|
} else if (reply == '$') {
|
|
LOG_ERROR("GDB missing ack(1) - assumed good");
|
|
gdb_putback_char(connection, reply);
|
|
return ERROR_OK;
|
|
} else {
|
|
LOG_ERROR("unknown character(1) 0x%2.2x in reply, dropping connection", reply);
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
} else if (reply == '$') {
|
|
LOG_ERROR("GDB missing ack(2) - assumed good");
|
|
gdb_putback_char(connection, reply);
|
|
return ERROR_OK;
|
|
} else {
|
|
LOG_ERROR("unknown character(2) 0x%2.2x in reply, dropping connection",
|
|
reply);
|
|
gdb_con->closed = true;
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
}
|
|
if (gdb_con->closed)
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
int gdb_put_packet(struct connection *connection, char *buffer, int len)
|
|
{
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
gdb_con->busy = true;
|
|
int retval = gdb_put_packet_inner(connection, buffer, len);
|
|
gdb_con->busy = false;
|
|
|
|
/* we sent some data, reset timer for keep alive messages */
|
|
kept_alive();
|
|
|
|
return retval;
|
|
}
|
|
|
|
static inline int fetch_packet(struct connection *connection,
|
|
int *checksum_ok, int noack, int *len, char *buffer)
|
|
{
|
|
unsigned char my_checksum = 0;
|
|
char checksum[3];
|
|
int character;
|
|
int retval = ERROR_OK;
|
|
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
my_checksum = 0;
|
|
int count = 0;
|
|
count = 0;
|
|
|
|
/* move this over into local variables to use registers and give the
|
|
* more freedom to optimize */
|
|
char *buf_p = gdb_con->buf_p;
|
|
int buf_cnt = gdb_con->buf_cnt;
|
|
|
|
for (;; ) {
|
|
/* The common case is that we have an entire packet with no escape chars.
|
|
* We need to leave at least 2 bytes in the buffer to have
|
|
* gdb_get_char() update various bits and bobs correctly.
|
|
*/
|
|
if ((buf_cnt > 2) && ((buf_cnt + count) < *len)) {
|
|
/* The compiler will struggle a bit with constant propagation and
|
|
* aliasing, so we help it by showing that these values do not
|
|
* change inside the loop
|
|
*/
|
|
int i;
|
|
char *buf = buf_p;
|
|
int run = buf_cnt - 2;
|
|
i = 0;
|
|
int done = 0;
|
|
while (i < run) {
|
|
character = *buf++;
|
|
i++;
|
|
if (character == '#') {
|
|
/* Danger! character can be '#' when esc is
|
|
* used so we need an explicit boolean for done here. */
|
|
done = 1;
|
|
break;
|
|
}
|
|
|
|
if (character == '}') {
|
|
/* data transmitted in binary mode (X packet)
|
|
* uses 0x7d as escape character */
|
|
my_checksum += character & 0xff;
|
|
character = *buf++;
|
|
i++;
|
|
my_checksum += character & 0xff;
|
|
buffer[count++] = (character ^ 0x20) & 0xff;
|
|
} else {
|
|
my_checksum += character & 0xff;
|
|
buffer[count++] = character & 0xff;
|
|
}
|
|
}
|
|
buf_p += i;
|
|
buf_cnt -= i;
|
|
if (done)
|
|
break;
|
|
}
|
|
if (count > *len) {
|
|
LOG_ERROR("packet buffer too small");
|
|
retval = ERROR_GDB_BUFFER_TOO_SMALL;
|
|
break;
|
|
}
|
|
|
|
retval = gdb_get_char_fast(connection, &character, &buf_p, &buf_cnt);
|
|
if (retval != ERROR_OK)
|
|
break;
|
|
|
|
if (character == '#')
|
|
break;
|
|
|
|
if (character == '}') {
|
|
/* data transmitted in binary mode (X packet)
|
|
* uses 0x7d as escape character */
|
|
my_checksum += character & 0xff;
|
|
|
|
retval = gdb_get_char_fast(connection, &character, &buf_p, &buf_cnt);
|
|
if (retval != ERROR_OK)
|
|
break;
|
|
|
|
my_checksum += character & 0xff;
|
|
buffer[count++] = (character ^ 0x20) & 0xff;
|
|
} else {
|
|
my_checksum += character & 0xff;
|
|
buffer[count++] = character & 0xff;
|
|
}
|
|
}
|
|
|
|
gdb_con->buf_p = buf_p;
|
|
gdb_con->buf_cnt = buf_cnt;
|
|
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
|
|
*len = count;
|
|
|
|
retval = gdb_get_char(connection, &character);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
checksum[0] = character;
|
|
retval = gdb_get_char(connection, &character);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
checksum[1] = character;
|
|
checksum[2] = 0;
|
|
|
|
if (!noack)
|
|
*checksum_ok = (my_checksum == strtoul(checksum, NULL, 16));
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_get_packet_inner(struct connection *connection,
|
|
char *buffer, int *len)
|
|
{
|
|
int character;
|
|
int retval;
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
|
|
while (1) {
|
|
do {
|
|
retval = gdb_get_char(connection, &character);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
LOG_DEBUG("character: '%c'", character);
|
|
#endif
|
|
|
|
switch (character) {
|
|
case '$':
|
|
break;
|
|
case '+':
|
|
/* According to the GDB documentation
|
|
* (https://sourceware.org/gdb/onlinedocs/gdb/Packet-Acknowledgment.html):
|
|
* "gdb sends a final `+` acknowledgment of the stub's `OK`
|
|
* response, which can be safely ignored by the stub."
|
|
* However OpenOCD server already is in noack mode at this
|
|
* point and instead of ignoring this it was emitting a
|
|
* warning. This code makes server ignore the first ACK
|
|
* that will be received after going into noack mode,
|
|
* warning only about subsequent ACK's. */
|
|
if (gdb_con->noack_mode > 1) {
|
|
LOG_WARNING("acknowledgment received, but no packet pending");
|
|
} else if (gdb_con->noack_mode) {
|
|
LOG_DEBUG("Received first acknowledgment after entering noack mode. Ignoring it.");
|
|
gdb_con->noack_mode = 2;
|
|
}
|
|
break;
|
|
case '-':
|
|
LOG_WARNING("negative acknowledgment, but no packet pending");
|
|
break;
|
|
case 0x3:
|
|
gdb_con->ctrl_c = 1;
|
|
*len = 0;
|
|
return ERROR_OK;
|
|
default:
|
|
LOG_WARNING("ignoring character 0x%x", character);
|
|
break;
|
|
}
|
|
} while (character != '$');
|
|
|
|
int checksum_ok = 0;
|
|
/* explicit code expansion here to get faster inlined code in -O3 by not
|
|
* calculating checksum */
|
|
if (gdb_con->noack_mode) {
|
|
retval = fetch_packet(connection, &checksum_ok, 1, len, buffer);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
} else {
|
|
retval = fetch_packet(connection, &checksum_ok, 0, len, buffer);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
}
|
|
|
|
if (gdb_con->noack_mode) {
|
|
/* checksum is not checked in noack mode */
|
|
break;
|
|
}
|
|
if (checksum_ok) {
|
|
retval = gdb_write(connection, "+", 1);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
break;
|
|
}
|
|
}
|
|
if (gdb_con->closed)
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_get_packet(struct connection *connection, char *buffer, int *len)
|
|
{
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
gdb_con->busy = true;
|
|
int retval = gdb_get_packet_inner(connection, buffer, len);
|
|
gdb_con->busy = false;
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_output_con(struct connection *connection, const char *line)
|
|
{
|
|
char *hex_buffer;
|
|
int bin_size;
|
|
|
|
bin_size = strlen(line);
|
|
|
|
hex_buffer = malloc(bin_size * 2 + 2);
|
|
if (hex_buffer == NULL)
|
|
return ERROR_GDB_BUFFER_TOO_SMALL;
|
|
|
|
hex_buffer[0] = 'O';
|
|
size_t pkt_len = hexify(hex_buffer + 1, (const uint8_t *)line, bin_size,
|
|
bin_size * 2 + 1);
|
|
int retval = gdb_put_packet(connection, hex_buffer, pkt_len + 1);
|
|
|
|
free(hex_buffer);
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_output(struct command_context *context, const char *line)
|
|
{
|
|
/* this will be dumped to the log and also sent as an O packet if possible */
|
|
LOG_USER_N("%s", line);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static void gdb_signal_reply(struct target *target, struct connection *connection)
|
|
{
|
|
struct gdb_connection *gdb_connection = connection->priv;
|
|
char sig_reply[45];
|
|
char stop_reason[20];
|
|
char current_thread[25];
|
|
int sig_reply_len;
|
|
int signal_var;
|
|
|
|
rtos_update_threads(target);
|
|
|
|
if (target->debug_reason == DBG_REASON_EXIT) {
|
|
sig_reply_len = snprintf(sig_reply, sizeof(sig_reply), "W00");
|
|
} else {
|
|
if (gdb_connection->ctrl_c) {
|
|
signal_var = 0x2;
|
|
} else
|
|
signal_var = gdb_last_signal(target);
|
|
|
|
stop_reason[0] = '\0';
|
|
if (target->debug_reason == DBG_REASON_WATCHPOINT) {
|
|
enum watchpoint_rw hit_wp_type;
|
|
target_addr_t hit_wp_address;
|
|
|
|
if (watchpoint_hit(target, &hit_wp_type, &hit_wp_address) == ERROR_OK) {
|
|
|
|
switch (hit_wp_type) {
|
|
case WPT_WRITE:
|
|
snprintf(stop_reason, sizeof(stop_reason),
|
|
"watch:%08" TARGET_PRIxADDR ";", hit_wp_address);
|
|
break;
|
|
case WPT_READ:
|
|
snprintf(stop_reason, sizeof(stop_reason),
|
|
"rwatch:%08" TARGET_PRIxADDR ";", hit_wp_address);
|
|
break;
|
|
case WPT_ACCESS:
|
|
snprintf(stop_reason, sizeof(stop_reason),
|
|
"awatch:%08" TARGET_PRIxADDR ";", hit_wp_address);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
current_thread[0] = '\0';
|
|
if (target->rtos != NULL) {
|
|
struct target *ct;
|
|
snprintf(current_thread, sizeof(current_thread), "thread:%016" PRIx64 ";",
|
|
target->rtos->current_thread);
|
|
target->rtos->current_threadid = target->rtos->current_thread;
|
|
target->rtos->gdb_target_for_threadid(connection, target->rtos->current_threadid, &ct);
|
|
if (!gdb_connection->ctrl_c)
|
|
signal_var = gdb_last_signal(ct);
|
|
}
|
|
|
|
sig_reply_len = snprintf(sig_reply, sizeof(sig_reply), "T%2.2x%s%s",
|
|
signal_var, stop_reason, current_thread);
|
|
|
|
gdb_connection->ctrl_c = 0;
|
|
}
|
|
|
|
gdb_put_packet(connection, sig_reply, sig_reply_len);
|
|
gdb_connection->frontend_state = TARGET_HALTED;
|
|
}
|
|
|
|
static void gdb_fileio_reply(struct target *target, struct connection *connection)
|
|
{
|
|
struct gdb_connection *gdb_connection = connection->priv;
|
|
char fileio_command[256];
|
|
int command_len;
|
|
bool program_exited = false;
|
|
|
|
if (strcmp(target->fileio_info->identifier, "open") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64 ",%" PRIx64 ",%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2,
|
|
target->fileio_info->param_3,
|
|
target->fileio_info->param_4);
|
|
else if (strcmp(target->fileio_info->identifier, "close") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1);
|
|
else if (strcmp(target->fileio_info->identifier, "read") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64 ",%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2,
|
|
target->fileio_info->param_3);
|
|
else if (strcmp(target->fileio_info->identifier, "write") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64 ",%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2,
|
|
target->fileio_info->param_3);
|
|
else if (strcmp(target->fileio_info->identifier, "lseek") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64 ",%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2,
|
|
target->fileio_info->param_3);
|
|
else if (strcmp(target->fileio_info->identifier, "rename") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64 ",%" PRIx64 "/%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2,
|
|
target->fileio_info->param_3,
|
|
target->fileio_info->param_4);
|
|
else if (strcmp(target->fileio_info->identifier, "unlink") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2);
|
|
else if (strcmp(target->fileio_info->identifier, "stat") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64 ",%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2,
|
|
target->fileio_info->param_3);
|
|
else if (strcmp(target->fileio_info->identifier, "fstat") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2);
|
|
else if (strcmp(target->fileio_info->identifier, "gettimeofday") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2);
|
|
else if (strcmp(target->fileio_info->identifier, "isatty") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1);
|
|
else if (strcmp(target->fileio_info->identifier, "system") == 0)
|
|
sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64, target->fileio_info->identifier,
|
|
target->fileio_info->param_1,
|
|
target->fileio_info->param_2);
|
|
else if (strcmp(target->fileio_info->identifier, "exit") == 0) {
|
|
/* If target hits exit syscall, report to GDB the program is terminated.
|
|
* In addition, let target run its own exit syscall handler. */
|
|
program_exited = true;
|
|
sprintf(fileio_command, "W%02" PRIx64, target->fileio_info->param_1);
|
|
} else {
|
|
LOG_DEBUG("Unknown syscall: %s", target->fileio_info->identifier);
|
|
|
|
/* encounter unknown syscall, continue */
|
|
gdb_connection->frontend_state = TARGET_RUNNING;
|
|
target_resume(target, 1, 0x0, 0, 0);
|
|
return;
|
|
}
|
|
|
|
command_len = strlen(fileio_command);
|
|
gdb_put_packet(connection, fileio_command, command_len);
|
|
|
|
if (program_exited) {
|
|
/* Use target_resume() to let target run its own exit syscall handler. */
|
|
gdb_connection->frontend_state = TARGET_RUNNING;
|
|
target_resume(target, 1, 0x0, 0, 0);
|
|
} else {
|
|
gdb_connection->frontend_state = TARGET_HALTED;
|
|
rtos_update_threads(target);
|
|
}
|
|
}
|
|
|
|
static void gdb_frontend_halted(struct target *target, struct connection *connection)
|
|
{
|
|
struct gdb_connection *gdb_connection = connection->priv;
|
|
|
|
/* In the GDB protocol when we are stepping or continuing execution,
|
|
* we have a lingering reply. Upon receiving a halted event
|
|
* when we have that lingering packet, we reply to the original
|
|
* step or continue packet.
|
|
*
|
|
* Executing monitor commands can bring the target in and
|
|
* out of the running state so we'll see lots of TARGET_EVENT_XXX
|
|
* that are to be ignored.
|
|
*/
|
|
if (gdb_connection->frontend_state == TARGET_RUNNING) {
|
|
/* stop forwarding log packets! */
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
|
|
/* check fileio first */
|
|
if (target_get_gdb_fileio_info(target, target->fileio_info) == ERROR_OK)
|
|
gdb_fileio_reply(target, connection);
|
|
else
|
|
gdb_signal_reply(target, connection);
|
|
}
|
|
}
|
|
|
|
static int gdb_target_callback_event_handler(struct target *target,
|
|
enum target_event event, void *priv)
|
|
{
|
|
int retval;
|
|
struct connection *connection = priv;
|
|
struct gdb_service *gdb_service = connection->service->priv;
|
|
|
|
if (gdb_service->target != target)
|
|
return ERROR_OK;
|
|
|
|
switch (event) {
|
|
case TARGET_EVENT_GDB_HALT:
|
|
gdb_frontend_halted(target, connection);
|
|
break;
|
|
case TARGET_EVENT_HALTED:
|
|
target_call_event_callbacks(target, TARGET_EVENT_GDB_END);
|
|
break;
|
|
case TARGET_EVENT_GDB_FLASH_ERASE_START:
|
|
retval = jtag_execute_queue();
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_new_connection(struct connection *connection)
|
|
{
|
|
struct gdb_connection *gdb_connection = malloc(sizeof(struct gdb_connection));
|
|
struct target *target;
|
|
int retval;
|
|
int initial_ack;
|
|
|
|
target = get_target_from_connection(connection);
|
|
connection->priv = gdb_connection;
|
|
connection->cmd_ctx->current_target = target;
|
|
|
|
/* initialize gdb connection information */
|
|
gdb_connection->buf_p = gdb_connection->buffer;
|
|
gdb_connection->buf_cnt = 0;
|
|
gdb_connection->ctrl_c = 0;
|
|
gdb_connection->frontend_state = TARGET_HALTED;
|
|
gdb_connection->vflash_image = NULL;
|
|
gdb_connection->closed = false;
|
|
gdb_connection->busy = false;
|
|
gdb_connection->noack_mode = 0;
|
|
gdb_connection->sync = false;
|
|
gdb_connection->mem_write_error = false;
|
|
gdb_connection->attached = true;
|
|
gdb_connection->target_desc.tdesc = NULL;
|
|
gdb_connection->target_desc.tdesc_length = 0;
|
|
gdb_connection->thread_list = NULL;
|
|
|
|
/* send ACK to GDB for debug request */
|
|
gdb_write(connection, "+", 1);
|
|
|
|
/* output goes through gdb connection */
|
|
command_set_output_handler(connection->cmd_ctx, gdb_output, connection);
|
|
|
|
/* we must remove all breakpoints registered to the target as a previous
|
|
* GDB session could leave dangling breakpoints if e.g. communication
|
|
* timed out.
|
|
*/
|
|
breakpoint_clear_target(target);
|
|
watchpoint_clear_target(target);
|
|
|
|
if (target->rtos) {
|
|
/* clean previous rtos session if supported*/
|
|
if (target->rtos->type->clean)
|
|
target->rtos->type->clean(target);
|
|
|
|
/* update threads */
|
|
rtos_update_threads(target);
|
|
}
|
|
|
|
/* remove the initial ACK from the incoming buffer */
|
|
retval = gdb_get_char(connection, &initial_ack);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
|
|
/* FIX!!!??? would we actually ever receive a + here???
|
|
* Not observed.
|
|
*/
|
|
if (initial_ack != '+')
|
|
gdb_putback_char(connection, initial_ack);
|
|
target_call_event_callbacks(target, TARGET_EVENT_GDB_ATTACH);
|
|
|
|
if (gdb_use_memory_map) {
|
|
/* Connect must fail if the memory map can't be set up correctly.
|
|
*
|
|
* This will cause an auto_probe to be invoked, which is either
|
|
* a no-op or it will fail when the target isn't ready(e.g. not halted).
|
|
*/
|
|
int i;
|
|
for (i = 0; i < flash_get_bank_count(); i++) {
|
|
struct flash_bank *p;
|
|
p = get_flash_bank_by_num_noprobe(i);
|
|
if (p->target != target)
|
|
continue;
|
|
retval = get_flash_bank_by_num(i, &p);
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("Connect failed. Consider setting up a gdb-attach event for the target " \
|
|
"to prepare target for GDB connect, or use 'gdb_memory_map disable'.");
|
|
return retval;
|
|
}
|
|
}
|
|
}
|
|
|
|
gdb_actual_connections++;
|
|
log_printf_lf(all_targets->next != NULL ? LOG_LVL_INFO : LOG_LVL_DEBUG,
|
|
__FILE__, __LINE__, __func__,
|
|
"New GDB Connection: %d, Target %s, state: %s",
|
|
gdb_actual_connections,
|
|
target_name(target),
|
|
target_state_name(target));
|
|
|
|
/* DANGER! If we fail subsequently, we must remove this handler,
|
|
* otherwise we occasionally see crashes as the timer can invoke the
|
|
* callback fn.
|
|
*
|
|
* register callback to be informed about target events */
|
|
target_register_event_callback(gdb_target_callback_event_handler, connection);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_connection_closed(struct connection *connection)
|
|
{
|
|
struct target *target;
|
|
struct gdb_connection *gdb_connection = connection->priv;
|
|
|
|
target = get_target_from_connection(connection);
|
|
|
|
/* we're done forwarding messages. Tear down callback before
|
|
* cleaning up connection.
|
|
*/
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
|
|
gdb_actual_connections--;
|
|
LOG_DEBUG("GDB Close, Target: %s, state: %s, gdb_actual_connections=%d",
|
|
target_name(target),
|
|
target_state_name(target),
|
|
gdb_actual_connections);
|
|
|
|
/* see if an image built with vFlash commands is left */
|
|
if (gdb_connection->vflash_image) {
|
|
image_close(gdb_connection->vflash_image);
|
|
free(gdb_connection->vflash_image);
|
|
gdb_connection->vflash_image = NULL;
|
|
}
|
|
|
|
/* if this connection registered a debug-message receiver delete it */
|
|
delete_debug_msg_receiver(connection->cmd_ctx, target);
|
|
|
|
if (connection->priv) {
|
|
free(connection->priv);
|
|
connection->priv = NULL;
|
|
} else
|
|
LOG_ERROR("BUG: connection->priv == NULL");
|
|
|
|
target_unregister_event_callback(gdb_target_callback_event_handler, connection);
|
|
|
|
target_call_event_callbacks(target, TARGET_EVENT_GDB_END);
|
|
|
|
target_call_event_callbacks(target, TARGET_EVENT_GDB_DETACH);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static void gdb_send_error(struct connection *connection, uint8_t the_error)
|
|
{
|
|
char err[4];
|
|
snprintf(err, 4, "E%2.2X", the_error);
|
|
gdb_put_packet(connection, err, 3);
|
|
}
|
|
|
|
static int gdb_last_signal_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
char sig_reply[4];
|
|
int signal_var;
|
|
|
|
if (!gdb_con->attached) {
|
|
/* if we are here we have received a kill packet
|
|
* reply W stop reply otherwise gdb gets very unhappy */
|
|
gdb_put_packet(connection, "W00", 3);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
signal_var = gdb_last_signal(target);
|
|
|
|
snprintf(sig_reply, 4, "S%2.2x", signal_var);
|
|
gdb_put_packet(connection, sig_reply, 3);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static inline int gdb_reg_pos(struct target *target, int pos, int len)
|
|
{
|
|
if (target->endianness == TARGET_LITTLE_ENDIAN)
|
|
return pos;
|
|
else
|
|
return len - 1 - pos;
|
|
}
|
|
|
|
/* Convert register to string of bytes. NB! The # of bits in the
|
|
* register might be non-divisible by 8(a byte), in which
|
|
* case an entire byte is shown.
|
|
*
|
|
* NB! the format on the wire is the target endianness
|
|
*
|
|
* The format of reg->value is little endian
|
|
*
|
|
*/
|
|
static void gdb_str_to_target(struct target *target,
|
|
char *tstr, struct reg *reg)
|
|
{
|
|
int i;
|
|
|
|
uint8_t *buf;
|
|
int buf_len;
|
|
buf = reg->value;
|
|
buf_len = DIV_ROUND_UP(reg->size, 8);
|
|
|
|
for (i = 0; i < buf_len; i++) {
|
|
int j = gdb_reg_pos(target, i, buf_len);
|
|
tstr += sprintf(tstr, "%02x", buf[j]);
|
|
}
|
|
}
|
|
|
|
/* copy over in register buffer */
|
|
static void gdb_target_to_reg(struct target *target,
|
|
char const *tstr, int str_len, uint8_t *bin)
|
|
{
|
|
if (str_len % 2) {
|
|
LOG_ERROR("BUG: gdb value with uneven number of characters encountered");
|
|
exit(-1);
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; i < str_len; i += 2) {
|
|
unsigned t;
|
|
if (sscanf(tstr + i, "%02x", &t) != 1) {
|
|
LOG_ERROR("BUG: unable to convert register value");
|
|
exit(-1);
|
|
}
|
|
|
|
int j = gdb_reg_pos(target, i/2, str_len/2);
|
|
bin[j] = t;
|
|
}
|
|
}
|
|
|
|
static int gdb_get_registers_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
struct reg **reg_list;
|
|
int reg_list_size;
|
|
int retval;
|
|
int reg_packet_size = 0;
|
|
char *reg_packet;
|
|
char *reg_packet_p;
|
|
int i;
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
LOG_DEBUG("-");
|
|
#endif
|
|
|
|
if ((target->rtos != NULL) && (ERROR_OK == rtos_get_gdb_reg_list(connection)))
|
|
return ERROR_OK;
|
|
|
|
retval = target_get_gdb_reg_list(target, ®_list, ®_list_size,
|
|
REG_CLASS_GENERAL);
|
|
if (retval != ERROR_OK)
|
|
return gdb_error(connection, retval);
|
|
|
|
for (i = 0; i < reg_list_size; i++)
|
|
reg_packet_size += DIV_ROUND_UP(reg_list[i]->size, 8) * 2;
|
|
|
|
assert(reg_packet_size > 0);
|
|
|
|
reg_packet = malloc(reg_packet_size + 1); /* plus one for string termination null */
|
|
if (reg_packet == NULL)
|
|
return ERROR_FAIL;
|
|
|
|
reg_packet_p = reg_packet;
|
|
|
|
for (i = 0; i < reg_list_size; i++) {
|
|
if (!reg_list[i]->valid) {
|
|
retval = reg_list[i]->type->get(reg_list[i]);
|
|
if (retval != ERROR_OK && gdb_report_register_access_error) {
|
|
LOG_DEBUG("Couldn't get register %s.", reg_list[i]->name);
|
|
free(reg_packet);
|
|
free(reg_list);
|
|
return gdb_error(connection, retval);
|
|
}
|
|
}
|
|
gdb_str_to_target(target, reg_packet_p, reg_list[i]);
|
|
reg_packet_p += DIV_ROUND_UP(reg_list[i]->size, 8) * 2;
|
|
}
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
{
|
|
char *reg_packet_p_debug;
|
|
reg_packet_p_debug = strndup(reg_packet, reg_packet_size);
|
|
LOG_DEBUG("reg_packet: %s", reg_packet_p_debug);
|
|
free(reg_packet_p_debug);
|
|
}
|
|
#endif
|
|
|
|
gdb_put_packet(connection, reg_packet, reg_packet_size);
|
|
free(reg_packet);
|
|
|
|
free(reg_list);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_set_registers_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
int i;
|
|
struct reg **reg_list;
|
|
int reg_list_size;
|
|
int retval;
|
|
char const *packet_p;
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
LOG_DEBUG("-");
|
|
#endif
|
|
|
|
/* skip command character */
|
|
packet++;
|
|
packet_size--;
|
|
|
|
if (packet_size % 2) {
|
|
LOG_WARNING("GDB set_registers packet with uneven characters received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
retval = target_get_gdb_reg_list(target, ®_list, ®_list_size,
|
|
REG_CLASS_GENERAL);
|
|
if (retval != ERROR_OK)
|
|
return gdb_error(connection, retval);
|
|
|
|
packet_p = packet;
|
|
for (i = 0; i < reg_list_size; i++) {
|
|
uint8_t *bin_buf;
|
|
int chars = (DIV_ROUND_UP(reg_list[i]->size, 8) * 2);
|
|
|
|
if (packet_p + chars > packet + packet_size)
|
|
LOG_ERROR("BUG: register packet is too small for registers");
|
|
|
|
bin_buf = malloc(DIV_ROUND_UP(reg_list[i]->size, 8));
|
|
gdb_target_to_reg(target, packet_p, chars, bin_buf);
|
|
|
|
retval = reg_list[i]->type->set(reg_list[i], bin_buf);
|
|
if (retval != ERROR_OK && gdb_report_register_access_error) {
|
|
LOG_DEBUG("Couldn't set register %s.", reg_list[i]->name);
|
|
free(reg_list);
|
|
free(bin_buf);
|
|
return gdb_error(connection, retval);
|
|
}
|
|
|
|
/* advance packet pointer */
|
|
packet_p += chars;
|
|
|
|
free(bin_buf);
|
|
}
|
|
|
|
/* free struct reg *reg_list[] array allocated by get_gdb_reg_list */
|
|
free(reg_list);
|
|
|
|
gdb_put_packet(connection, "OK", 2);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_get_register_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
char *reg_packet;
|
|
int reg_num = strtoul(packet + 1, NULL, 16);
|
|
struct reg **reg_list;
|
|
int reg_list_size;
|
|
int retval;
|
|
|
|
#ifdef _DEBUG_GDB_IO_
|
|
LOG_DEBUG("-");
|
|
#endif
|
|
|
|
retval = target_get_gdb_reg_list(target, ®_list, ®_list_size,
|
|
REG_CLASS_ALL);
|
|
if (retval != ERROR_OK)
|
|
return gdb_error(connection, retval);
|
|
|
|
if (reg_list_size <= reg_num) {
|
|
LOG_ERROR("gdb requested a non-existing register");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
if (!reg_list[reg_num]->valid) {
|
|
retval = reg_list[reg_num]->type->get(reg_list[reg_num]);
|
|
if (retval != ERROR_OK && gdb_report_register_access_error) {
|
|
LOG_DEBUG("Couldn't get register %s.", reg_list[reg_num]->name);
|
|
free(reg_list);
|
|
return gdb_error(connection, retval);
|
|
}
|
|
}
|
|
|
|
reg_packet = malloc(DIV_ROUND_UP(reg_list[reg_num]->size, 8) * 2 + 1); /* plus one for string termination null */
|
|
|
|
gdb_str_to_target(target, reg_packet, reg_list[reg_num]);
|
|
|
|
gdb_put_packet(connection, reg_packet, DIV_ROUND_UP(reg_list[reg_num]->size, 8) * 2);
|
|
|
|
free(reg_list);
|
|
free(reg_packet);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_set_register_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
char *separator;
|
|
uint8_t *bin_buf;
|
|
int reg_num = strtoul(packet + 1, &separator, 16);
|
|
struct reg **reg_list;
|
|
int reg_list_size;
|
|
int retval;
|
|
|
|
LOG_DEBUG("-");
|
|
|
|
retval = target_get_gdb_reg_list(target, ®_list, ®_list_size,
|
|
REG_CLASS_ALL);
|
|
if (retval != ERROR_OK)
|
|
return gdb_error(connection, retval);
|
|
|
|
if (reg_list_size <= reg_num) {
|
|
LOG_ERROR("gdb requested a non-existing register");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
if (*separator != '=') {
|
|
LOG_ERROR("GDB 'set register packet', but no '=' following the register number");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
/* convert from GDB-string (target-endian) to hex-string (big-endian) */
|
|
bin_buf = malloc(DIV_ROUND_UP(reg_list[reg_num]->size, 8));
|
|
int chars = (DIV_ROUND_UP(reg_list[reg_num]->size, 8) * 2);
|
|
|
|
if ((unsigned int)chars != strlen(separator + 1)) {
|
|
LOG_ERROR("gdb sent %zu bits for a %d-bit register (%s)",
|
|
strlen(separator + 1) * 4, chars * 4, reg_list[reg_num]->name);
|
|
free(bin_buf);
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
gdb_target_to_reg(target, separator + 1, chars, bin_buf);
|
|
|
|
retval = reg_list[reg_num]->type->set(reg_list[reg_num], bin_buf);
|
|
if (retval != ERROR_OK && gdb_report_register_access_error) {
|
|
LOG_DEBUG("Couldn't set register %s.", reg_list[reg_num]->name);
|
|
free(bin_buf);
|
|
free(reg_list);
|
|
return gdb_error(connection, retval);
|
|
}
|
|
|
|
gdb_put_packet(connection, "OK", 2);
|
|
|
|
free(bin_buf);
|
|
free(reg_list);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* No attempt is made to translate the "retval" to
|
|
* GDB speak. This has to be done at the calling
|
|
* site as no mapping really exists.
|
|
*/
|
|
static int gdb_error(struct connection *connection, int retval)
|
|
{
|
|
LOG_DEBUG("Reporting %i to GDB as generic error", retval);
|
|
gdb_send_error(connection, EFAULT);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* We don't have to worry about the default 2 second timeout for GDB packets,
|
|
* because GDB breaks up large memory reads into smaller reads.
|
|
*
|
|
* 8191 bytes by the looks of it. Why 8191 bytes instead of 8192?????
|
|
*/
|
|
static int gdb_read_memory_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
char *separator;
|
|
uint64_t addr = 0;
|
|
uint32_t len = 0;
|
|
|
|
uint8_t *buffer;
|
|
char *hex_buffer;
|
|
|
|
int retval = ERROR_OK;
|
|
|
|
/* skip command character */
|
|
packet++;
|
|
|
|
addr = strtoull(packet, &separator, 16);
|
|
|
|
if (*separator != ',') {
|
|
LOG_ERROR("incomplete read memory packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
len = strtoul(separator + 1, NULL, 16);
|
|
|
|
if (!len) {
|
|
LOG_WARNING("invalid read memory packet received (len == 0)");
|
|
gdb_put_packet(connection, NULL, 0);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
buffer = malloc(len);
|
|
|
|
LOG_DEBUG("addr: 0x%16.16" PRIx64 ", len: 0x%8.8" PRIx32 "", addr, len);
|
|
|
|
retval = target_read_buffer(target, addr, len, buffer);
|
|
|
|
if ((retval != ERROR_OK) && !gdb_report_data_abort) {
|
|
/* TODO : Here we have to lie and send back all zero's lest stack traces won't work.
|
|
* At some point this might be fixed in GDB, in which case this code can be removed.
|
|
*
|
|
* OpenOCD developers are acutely aware of this problem, but there is nothing
|
|
* gained by involving the user in this problem that hopefully will get resolved
|
|
* eventually
|
|
*
|
|
* http://sourceware.org/cgi-bin/gnatsweb.pl? \
|
|
* cmd = view%20audit-trail&database = gdb&pr = 2395
|
|
*
|
|
* For now, the default is to fix up things to make current GDB versions work.
|
|
* This can be overwritten using the gdb_report_data_abort <'enable'|'disable'> command.
|
|
*/
|
|
memset(buffer, 0, len);
|
|
retval = ERROR_OK;
|
|
}
|
|
|
|
if (retval == ERROR_OK) {
|
|
hex_buffer = malloc(len * 2 + 1);
|
|
|
|
size_t pkt_len = hexify(hex_buffer, buffer, len, len * 2 + 1);
|
|
|
|
gdb_put_packet(connection, hex_buffer, pkt_len);
|
|
|
|
free(hex_buffer);
|
|
} else
|
|
retval = gdb_error(connection, retval);
|
|
|
|
free(buffer);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_write_memory_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
char *separator;
|
|
uint64_t addr = 0;
|
|
uint32_t len = 0;
|
|
|
|
uint8_t *buffer;
|
|
int retval;
|
|
|
|
/* skip command character */
|
|
packet++;
|
|
|
|
addr = strtoull(packet, &separator, 16);
|
|
|
|
if (*separator != ',') {
|
|
LOG_ERROR("incomplete write memory packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
len = strtoul(separator + 1, &separator, 16);
|
|
|
|
if (*(separator++) != ':') {
|
|
LOG_ERROR("incomplete write memory packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
buffer = malloc(len);
|
|
|
|
LOG_DEBUG("addr: 0x%" PRIx64 ", len: 0x%8.8" PRIx32 "", addr, len);
|
|
|
|
if (unhexify(buffer, separator, len) != len)
|
|
LOG_ERROR("unable to decode memory packet");
|
|
|
|
retval = target_write_buffer(target, addr, len, buffer);
|
|
|
|
if (retval == ERROR_OK)
|
|
gdb_put_packet(connection, "OK", 2);
|
|
else
|
|
retval = gdb_error(connection, retval);
|
|
|
|
free(buffer);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_write_memory_binary_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
char *separator;
|
|
uint64_t addr = 0;
|
|
uint32_t len = 0;
|
|
|
|
int retval = ERROR_OK;
|
|
/* Packets larger than fast_limit bytes will be acknowledged instantly on
|
|
* the assumption that we're in a download and it's important to go as fast
|
|
* as possible. */
|
|
uint32_t fast_limit = 8;
|
|
|
|
/* skip command character */
|
|
packet++;
|
|
|
|
addr = strtoull(packet, &separator, 16);
|
|
|
|
if (*separator != ',') {
|
|
LOG_ERROR("incomplete write memory binary packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
len = strtoul(separator + 1, &separator, 16);
|
|
|
|
if (*(separator++) != ':') {
|
|
LOG_ERROR("incomplete write memory binary packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
struct gdb_connection *gdb_connection = connection->priv;
|
|
|
|
if (gdb_connection->mem_write_error)
|
|
retval = ERROR_FAIL;
|
|
|
|
if (retval == ERROR_OK) {
|
|
if (len >= fast_limit) {
|
|
/* By replying the packet *immediately* GDB will send us a new packet
|
|
* while we write the last one to the target.
|
|
* We only do this for larger writes, so that users who do something like:
|
|
* p *((int*)0xdeadbeef)=8675309
|
|
* will get immediate feedback that that write failed.
|
|
*/
|
|
gdb_put_packet(connection, "OK", 2);
|
|
}
|
|
} else {
|
|
retval = gdb_error(connection, retval);
|
|
/* now that we have reported the memory write error, we can clear the condition */
|
|
gdb_connection->mem_write_error = false;
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
}
|
|
|
|
if (len) {
|
|
LOG_DEBUG("addr: 0x%" PRIx64 ", len: 0x%8.8" PRIx32 "", addr, len);
|
|
|
|
retval = target_write_buffer(target, addr, len, (uint8_t *)separator);
|
|
if (retval != ERROR_OK)
|
|
gdb_connection->mem_write_error = true;
|
|
}
|
|
|
|
if (len < fast_limit) {
|
|
if (retval != ERROR_OK) {
|
|
gdb_error(connection, retval);
|
|
gdb_connection->mem_write_error = false;
|
|
} else {
|
|
gdb_put_packet(connection, "OK", 2);
|
|
}
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_step_continue_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
int current = 0;
|
|
uint64_t address = 0x0;
|
|
int retval = ERROR_OK;
|
|
|
|
LOG_DEBUG("-");
|
|
|
|
if (packet_size > 1)
|
|
address = strtoull(packet + 1, NULL, 16);
|
|
else
|
|
current = 1;
|
|
|
|
gdb_running_type = packet[0];
|
|
if (packet[0] == 'c') {
|
|
LOG_DEBUG("continue");
|
|
/* resume at current address, don't handle breakpoints, not debugging */
|
|
retval = target_resume(target, current, address, 0, 0);
|
|
} else if (packet[0] == 's') {
|
|
LOG_DEBUG("step");
|
|
/* step at current or address, don't handle breakpoints */
|
|
retval = target_step(target, current, address, 0);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_breakpoint_watchpoint_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
int type;
|
|
enum breakpoint_type bp_type = BKPT_SOFT /* dummy init to avoid warning */;
|
|
enum watchpoint_rw wp_type = WPT_READ /* dummy init to avoid warning */;
|
|
uint64_t address;
|
|
uint32_t size;
|
|
char *separator;
|
|
int retval;
|
|
|
|
LOG_DEBUG("-");
|
|
|
|
type = strtoul(packet + 1, &separator, 16);
|
|
|
|
if (type == 0) /* memory breakpoint */
|
|
bp_type = BKPT_SOFT;
|
|
else if (type == 1) /* hardware breakpoint */
|
|
bp_type = BKPT_HARD;
|
|
else if (type == 2) /* write watchpoint */
|
|
wp_type = WPT_WRITE;
|
|
else if (type == 3) /* read watchpoint */
|
|
wp_type = WPT_READ;
|
|
else if (type == 4) /* access watchpoint */
|
|
wp_type = WPT_ACCESS;
|
|
else {
|
|
LOG_ERROR("invalid gdb watch/breakpoint type(%d), dropping connection", type);
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
if (gdb_breakpoint_override && ((bp_type == BKPT_SOFT) || (bp_type == BKPT_HARD)))
|
|
bp_type = gdb_breakpoint_override_type;
|
|
|
|
if (*separator != ',') {
|
|
LOG_ERROR("incomplete breakpoint/watchpoint packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
address = strtoull(separator + 1, &separator, 16);
|
|
|
|
if (*separator != ',') {
|
|
LOG_ERROR("incomplete breakpoint/watchpoint packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
size = strtoul(separator + 1, &separator, 16);
|
|
|
|
switch (type) {
|
|
case 0:
|
|
case 1:
|
|
if (packet[0] == 'Z') {
|
|
retval = breakpoint_add(target, address, size, bp_type);
|
|
if (retval != ERROR_OK) {
|
|
retval = gdb_error(connection, retval);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
} else
|
|
gdb_put_packet(connection, "OK", 2);
|
|
} else {
|
|
breakpoint_remove(target, address);
|
|
gdb_put_packet(connection, "OK", 2);
|
|
}
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
{
|
|
if (packet[0] == 'Z') {
|
|
retval = watchpoint_add(target, address, size, wp_type, 0, 0xffffffffu);
|
|
if (retval != ERROR_OK) {
|
|
retval = gdb_error(connection, retval);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
} else
|
|
gdb_put_packet(connection, "OK", 2);
|
|
} else {
|
|
watchpoint_remove(target, address);
|
|
gdb_put_packet(connection, "OK", 2);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* print out a string and allocate more space as needed,
|
|
* mainly used for XML at this point
|
|
*/
|
|
static void xml_printf(int *retval, char **xml, int *pos, int *size,
|
|
const char *fmt, ...)
|
|
{
|
|
if (*retval != ERROR_OK)
|
|
return;
|
|
int first = 1;
|
|
|
|
for (;; ) {
|
|
if ((*xml == NULL) || (!first)) {
|
|
/* start by 0 to exercise all the code paths.
|
|
* Need minimum 2 bytes to fit 1 char and 0 terminator. */
|
|
|
|
*size = *size * 2 + 2;
|
|
char *t = *xml;
|
|
*xml = realloc(*xml, *size);
|
|
if (*xml == NULL) {
|
|
if (t)
|
|
free(t);
|
|
*retval = ERROR_SERVER_REMOTE_CLOSED;
|
|
return;
|
|
}
|
|
}
|
|
|
|
va_list ap;
|
|
int ret;
|
|
va_start(ap, fmt);
|
|
ret = vsnprintf(*xml + *pos, *size - *pos, fmt, ap);
|
|
va_end(ap);
|
|
if ((ret > 0) && ((ret + 1) < *size - *pos)) {
|
|
*pos += ret;
|
|
return;
|
|
}
|
|
/* there was just enough or not enough space, allocate more. */
|
|
first = 0;
|
|
}
|
|
}
|
|
|
|
static int decode_xfer_read(char const *buf, char **annex, int *ofs, unsigned int *len)
|
|
{
|
|
/* Locate the annex. */
|
|
const char *annex_end = strchr(buf, ':');
|
|
if (annex_end == NULL)
|
|
return ERROR_FAIL;
|
|
|
|
/* After the read marker and annex, qXfer looks like a
|
|
* traditional 'm' packet. */
|
|
char *separator;
|
|
*ofs = strtoul(annex_end + 1, &separator, 16);
|
|
|
|
if (*separator != ',')
|
|
return ERROR_FAIL;
|
|
|
|
*len = strtoul(separator + 1, NULL, 16);
|
|
|
|
/* Extract the annex if needed */
|
|
if (annex != NULL) {
|
|
*annex = strndup(buf, annex_end - buf);
|
|
if (*annex == NULL)
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int compare_bank(const void *a, const void *b)
|
|
{
|
|
struct flash_bank *b1, *b2;
|
|
b1 = *((struct flash_bank **)a);
|
|
b2 = *((struct flash_bank **)b);
|
|
|
|
if (b1->base == b2->base)
|
|
return 0;
|
|
else if (b1->base > b2->base)
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
static int gdb_memory_map(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
/* We get away with only specifying flash here. Regions that are not
|
|
* specified are treated as if we provided no memory map(if not we
|
|
* could detect the holes and mark them as RAM).
|
|
* Normally we only execute this code once, but no big deal if we
|
|
* have to regenerate it a couple of times.
|
|
*/
|
|
|
|
struct target *target = get_target_from_connection(connection);
|
|
struct flash_bank *p;
|
|
char *xml = NULL;
|
|
int size = 0;
|
|
int pos = 0;
|
|
int retval = ERROR_OK;
|
|
struct flash_bank **banks;
|
|
int offset;
|
|
int length;
|
|
char *separator;
|
|
target_addr_t ram_start = 0;
|
|
int i;
|
|
int target_flash_banks = 0;
|
|
|
|
/* skip command character */
|
|
packet += 23;
|
|
|
|
offset = strtoul(packet, &separator, 16);
|
|
length = strtoul(separator + 1, &separator, 16);
|
|
|
|
xml_printf(&retval, &xml, &pos, &size, "<memory-map>\n");
|
|
|
|
/* Sort banks in ascending order. We need to report non-flash
|
|
* memory as ram (or rather read/write) by default for GDB, since
|
|
* it has no concept of non-cacheable read/write memory (i/o etc).
|
|
*/
|
|
banks = malloc(sizeof(struct flash_bank *)*flash_get_bank_count());
|
|
|
|
for (i = 0; i < flash_get_bank_count(); i++) {
|
|
p = get_flash_bank_by_num_noprobe(i);
|
|
if (p->target != target)
|
|
continue;
|
|
retval = get_flash_bank_by_num(i, &p);
|
|
if (retval != ERROR_OK) {
|
|
free(banks);
|
|
gdb_error(connection, retval);
|
|
return retval;
|
|
}
|
|
banks[target_flash_banks++] = p;
|
|
}
|
|
|
|
qsort(banks, target_flash_banks, sizeof(struct flash_bank *),
|
|
compare_bank);
|
|
|
|
for (i = 0; i < target_flash_banks; i++) {
|
|
int j;
|
|
unsigned sector_size = 0;
|
|
unsigned group_len = 0;
|
|
|
|
p = banks[i];
|
|
|
|
if (ram_start < p->base)
|
|
xml_printf(&retval, &xml, &pos, &size,
|
|
"<memory type=\"ram\" start=\"" TARGET_ADDR_FMT "\" "
|
|
"length=\"0x%x\"/>\n",
|
|
ram_start, p->base - ram_start);
|
|
|
|
/* Report adjacent groups of same-size sectors. So for
|
|
* example top boot CFI flash will list an initial region
|
|
* with several large sectors (maybe 128KB) and several
|
|
* smaller ones at the end (maybe 32KB). STR7 will have
|
|
* regions with 8KB, 32KB, and 64KB sectors; etc.
|
|
*/
|
|
for (j = 0; j < p->num_sectors; j++) {
|
|
|
|
/* Maybe start a new group of sectors. */
|
|
if (sector_size == 0) {
|
|
if (p->sectors[j].offset + p->sectors[j].size > p->size) {
|
|
LOG_WARNING("The flash sector at offset 0x%08" PRIx32
|
|
" overflows the end of %s bank.",
|
|
p->sectors[j].offset, p->name);
|
|
LOG_WARNING("The rest of bank will not show in gdb memory map.");
|
|
break;
|
|
}
|
|
target_addr_t start;
|
|
start = p->base + p->sectors[j].offset;
|
|
xml_printf(&retval, &xml, &pos, &size,
|
|
"<memory type=\"flash\" "
|
|
"start=\"" TARGET_ADDR_FMT "\" ",
|
|
start);
|
|
sector_size = p->sectors[j].size;
|
|
group_len = sector_size;
|
|
} else {
|
|
group_len += sector_size; /* equal to p->sectors[j].size */
|
|
}
|
|
|
|
/* Does this finish a group of sectors?
|
|
* If not, continue an already-started group.
|
|
*/
|
|
if (j < p->num_sectors - 1
|
|
&& p->sectors[j + 1].size == sector_size
|
|
&& p->sectors[j + 1].offset == p->sectors[j].offset + sector_size
|
|
&& p->sectors[j + 1].offset + p->sectors[j + 1].size <= p->size)
|
|
continue;
|
|
|
|
xml_printf(&retval, &xml, &pos, &size,
|
|
"length=\"0x%x\">\n"
|
|
"<property name=\"blocksize\">"
|
|
"0x%x</property>\n"
|
|
"</memory>\n",
|
|
group_len,
|
|
sector_size);
|
|
sector_size = 0;
|
|
}
|
|
|
|
ram_start = p->base + p->size;
|
|
}
|
|
|
|
if (ram_start != 0)
|
|
xml_printf(&retval, &xml, &pos, &size,
|
|
"<memory type=\"ram\" start=\"" TARGET_ADDR_FMT "\" "
|
|
"length=\"0x%x\"/>\n",
|
|
ram_start, 0-ram_start);
|
|
/* ELSE a flash chip could be at the very end of the 32 bit address
|
|
* space, in which case ram_start will be precisely 0
|
|
*/
|
|
|
|
free(banks);
|
|
|
|
xml_printf(&retval, &xml, &pos, &size, "</memory-map>\n");
|
|
|
|
if (retval != ERROR_OK) {
|
|
free(xml);
|
|
gdb_error(connection, retval);
|
|
return retval;
|
|
}
|
|
|
|
if (offset + length > pos)
|
|
length = pos - offset;
|
|
|
|
char *t = malloc(length + 1);
|
|
t[0] = 'l';
|
|
memcpy(t + 1, xml + offset, length);
|
|
gdb_put_packet(connection, t, length + 1);
|
|
|
|
free(t);
|
|
free(xml);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static const char *gdb_get_reg_type_name(enum reg_type type)
|
|
{
|
|
switch (type) {
|
|
case REG_TYPE_BOOL:
|
|
return "bool";
|
|
case REG_TYPE_INT:
|
|
return "int";
|
|
case REG_TYPE_INT8:
|
|
return "int8";
|
|
case REG_TYPE_INT16:
|
|
return "int16";
|
|
case REG_TYPE_INT32:
|
|
return "int32";
|
|
case REG_TYPE_INT64:
|
|
return "int64";
|
|
case REG_TYPE_INT128:
|
|
return "int128";
|
|
case REG_TYPE_UINT:
|
|
return "uint";
|
|
case REG_TYPE_UINT8:
|
|
return "uint8";
|
|
case REG_TYPE_UINT16:
|
|
return "uint16";
|
|
case REG_TYPE_UINT32:
|
|
return "uint32";
|
|
case REG_TYPE_UINT64:
|
|
return "uint64";
|
|
case REG_TYPE_UINT128:
|
|
return "uint128";
|
|
case REG_TYPE_CODE_PTR:
|
|
return "code_ptr";
|
|
case REG_TYPE_DATA_PTR:
|
|
return "data_ptr";
|
|
case REG_TYPE_FLOAT:
|
|
return "float";
|
|
case REG_TYPE_IEEE_SINGLE:
|
|
return "ieee_single";
|
|
case REG_TYPE_IEEE_DOUBLE:
|
|
return "ieee_double";
|
|
case REG_TYPE_ARCH_DEFINED:
|
|
return "int"; /* return arbitrary string to avoid compile warning. */
|
|
}
|
|
|
|
return "int"; /* "int" as default value */
|
|
}
|
|
|
|
static int lookup_add_arch_defined_types(char const **arch_defined_types_list[], const char *type_id,
|
|
int *num_arch_defined_types)
|
|
{
|
|
int tbl_sz = *num_arch_defined_types;
|
|
|
|
if (type_id != NULL && (strcmp(type_id, ""))) {
|
|
for (int j = 0; j < (tbl_sz + 1); j++) {
|
|
if (!((*arch_defined_types_list)[j])) {
|
|
(*arch_defined_types_list)[tbl_sz++] = type_id;
|
|
*arch_defined_types_list = realloc(*arch_defined_types_list,
|
|
sizeof(char *) * (tbl_sz + 1));
|
|
(*arch_defined_types_list)[tbl_sz] = NULL;
|
|
*num_arch_defined_types = tbl_sz;
|
|
return 1;
|
|
} else {
|
|
if (!strcmp((*arch_defined_types_list)[j], type_id))
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int gdb_generate_reg_type_description(struct target *target,
|
|
char **tdesc, int *pos, int *size, struct reg_data_type *type,
|
|
char const **arch_defined_types_list[], int * num_arch_defined_types)
|
|
{
|
|
int retval = ERROR_OK;
|
|
|
|
if (type->type_class == REG_TYPE_CLASS_VECTOR) {
|
|
struct reg_data_type *data_type = type->reg_type_vector->type;
|
|
if (data_type->type == REG_TYPE_ARCH_DEFINED) {
|
|
if (lookup_add_arch_defined_types(arch_defined_types_list, data_type->id,
|
|
num_arch_defined_types))
|
|
gdb_generate_reg_type_description(target, tdesc, pos, size, data_type,
|
|
arch_defined_types_list,
|
|
num_arch_defined_types);
|
|
}
|
|
/* <vector id="id" type="type" count="count"/> */
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<vector id=\"%s\" type=\"%s\" count=\"%d\"/>\n",
|
|
type->id, type->reg_type_vector->type->id,
|
|
type->reg_type_vector->count);
|
|
|
|
} else if (type->type_class == REG_TYPE_CLASS_UNION) {
|
|
struct reg_data_type_union_field *field;
|
|
field = type->reg_type_union->fields;
|
|
while (field != NULL) {
|
|
struct reg_data_type *data_type = field->type;
|
|
if (data_type->type == REG_TYPE_ARCH_DEFINED) {
|
|
if (lookup_add_arch_defined_types(arch_defined_types_list, data_type->id,
|
|
num_arch_defined_types))
|
|
gdb_generate_reg_type_description(target, tdesc, pos, size, data_type,
|
|
arch_defined_types_list,
|
|
num_arch_defined_types);
|
|
}
|
|
|
|
field = field->next;
|
|
}
|
|
/* <union id="id">
|
|
* <field name="name" type="type"/> ...
|
|
* </union> */
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<union id=\"%s\">\n",
|
|
type->id);
|
|
|
|
field = type->reg_type_union->fields;
|
|
while (field != NULL) {
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<field name=\"%s\" type=\"%s\"/>\n",
|
|
field->name, field->type->id);
|
|
|
|
field = field->next;
|
|
}
|
|
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"</union>\n");
|
|
|
|
} else if (type->type_class == REG_TYPE_CLASS_STRUCT) {
|
|
struct reg_data_type_struct_field *field;
|
|
field = type->reg_type_struct->fields;
|
|
|
|
if (field->use_bitfields) {
|
|
/* <struct id="id" size="size">
|
|
* <field name="name" start="start" end="end"/> ...
|
|
* </struct> */
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<struct id=\"%s\" size=\"%d\">\n",
|
|
type->id, type->reg_type_struct->size);
|
|
while (field != NULL) {
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<field name=\"%s\" start=\"%d\" end=\"%d\" type=\"%s\" />\n",
|
|
field->name, field->bitfield->start, field->bitfield->end,
|
|
gdb_get_reg_type_name(field->bitfield->type));
|
|
|
|
field = field->next;
|
|
}
|
|
} else {
|
|
while (field != NULL) {
|
|
struct reg_data_type *data_type = field->type;
|
|
if (data_type->type == REG_TYPE_ARCH_DEFINED) {
|
|
if (lookup_add_arch_defined_types(arch_defined_types_list, data_type->id,
|
|
num_arch_defined_types))
|
|
gdb_generate_reg_type_description(target, tdesc, pos, size, data_type,
|
|
arch_defined_types_list,
|
|
num_arch_defined_types);
|
|
}
|
|
}
|
|
|
|
/* <struct id="id">
|
|
* <field name="name" type="type"/> ...
|
|
* </struct> */
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<struct id=\"%s\">\n",
|
|
type->id);
|
|
while (field != NULL) {
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<field name=\"%s\" type=\"%s\"/>\n",
|
|
field->name, field->type->id);
|
|
|
|
field = field->next;
|
|
}
|
|
}
|
|
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"</struct>\n");
|
|
|
|
} else if (type->type_class == REG_TYPE_CLASS_FLAGS) {
|
|
/* <flags id="id" size="size">
|
|
* <field name="name" start="start" end="end"/> ...
|
|
* </flags> */
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<flags id=\"%s\" size=\"%d\">\n",
|
|
type->id, type->reg_type_flags->size);
|
|
|
|
struct reg_data_type_flags_field *field;
|
|
field = type->reg_type_flags->fields;
|
|
while (field != NULL) {
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"<field name=\"%s\" start=\"%d\" end=\"%d\" type=\"%s\" />\n",
|
|
field->name, field->bitfield->start, field->bitfield->end,
|
|
gdb_get_reg_type_name(field->bitfield->type));
|
|
|
|
field = field->next;
|
|
}
|
|
|
|
xml_printf(&retval, tdesc, pos, size,
|
|
"</flags>\n");
|
|
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* Get a list of available target registers features. feature_list must
|
|
* be freed by caller.
|
|
*/
|
|
static int get_reg_features_list(struct target *target, char const **feature_list[], int *feature_list_size,
|
|
struct reg **reg_list, int reg_list_size)
|
|
{
|
|
int tbl_sz = 0;
|
|
|
|
/* Start with only one element */
|
|
*feature_list = calloc(1, sizeof(char *));
|
|
|
|
for (int i = 0; i < reg_list_size; i++) {
|
|
if (reg_list[i]->exist == false)
|
|
continue;
|
|
|
|
if (reg_list[i]->feature != NULL
|
|
&& reg_list[i]->feature->name != NULL
|
|
&& (strcmp(reg_list[i]->feature->name, ""))) {
|
|
/* We found a feature, check if the feature is already in the
|
|
* table. If not, allocate a new entry for the table and
|
|
* put the new feature in it.
|
|
*/
|
|
for (int j = 0; j < (tbl_sz + 1); j++) {
|
|
if (!((*feature_list)[j])) {
|
|
(*feature_list)[tbl_sz++] = reg_list[i]->feature->name;
|
|
*feature_list = realloc(*feature_list, sizeof(char *) * (tbl_sz + 1));
|
|
(*feature_list)[tbl_sz] = NULL;
|
|
break;
|
|
} else {
|
|
if (!strcmp((*feature_list)[j], reg_list[i]->feature->name))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (feature_list_size)
|
|
*feature_list_size = tbl_sz;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_generate_target_description(struct target *target, char **tdesc_out)
|
|
{
|
|
int retval = ERROR_OK;
|
|
struct reg **reg_list = NULL;
|
|
int reg_list_size;
|
|
char const **features = NULL;
|
|
char const **arch_defined_types = NULL;
|
|
int feature_list_size = 0;
|
|
int num_arch_defined_types = 0;
|
|
char *tdesc = NULL;
|
|
int pos = 0;
|
|
int size = 0;
|
|
|
|
arch_defined_types = calloc(1, sizeof(char *));
|
|
|
|
retval = target_get_gdb_reg_list(target, ®_list,
|
|
®_list_size, REG_CLASS_ALL);
|
|
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("get register list failed");
|
|
retval = ERROR_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
if (reg_list_size <= 0) {
|
|
LOG_ERROR("get register list failed");
|
|
retval = ERROR_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
/* Get a list of available target registers features */
|
|
retval = get_reg_features_list(target, &features, &feature_list_size, reg_list, reg_list_size);
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("Can't get the registers feature list");
|
|
retval = ERROR_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
/* If we found some features associated with registers, create sections */
|
|
int current_feature = 0;
|
|
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
"<?xml version=\"1.0\"?>\n"
|
|
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
|
|
"<target version=\"1.0\">\n");
|
|
|
|
/* generate target description according to register list */
|
|
if (features != NULL) {
|
|
while (features[current_feature]) {
|
|
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
"<feature name=\"%s\">\n",
|
|
features[current_feature]);
|
|
|
|
int i;
|
|
for (i = 0; i < reg_list_size; i++) {
|
|
|
|
if (reg_list[i]->exist == false)
|
|
continue;
|
|
|
|
if (strcmp(reg_list[i]->feature->name, features[current_feature]))
|
|
continue;
|
|
|
|
const char *type_str;
|
|
if (reg_list[i]->reg_data_type != NULL) {
|
|
if (reg_list[i]->reg_data_type->type == REG_TYPE_ARCH_DEFINED) {
|
|
/* generate <type... first, if there are architecture-defined types. */
|
|
if (lookup_add_arch_defined_types(&arch_defined_types,
|
|
reg_list[i]->reg_data_type->id,
|
|
&num_arch_defined_types))
|
|
gdb_generate_reg_type_description(target, &tdesc, &pos, &size,
|
|
reg_list[i]->reg_data_type,
|
|
&arch_defined_types,
|
|
&num_arch_defined_types);
|
|
|
|
type_str = reg_list[i]->reg_data_type->id;
|
|
} else {
|
|
/* predefined type */
|
|
type_str = gdb_get_reg_type_name(
|
|
reg_list[i]->reg_data_type->type);
|
|
}
|
|
} else {
|
|
/* Default type is "int" */
|
|
type_str = "int";
|
|
}
|
|
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
"<reg name=\"%s\"", reg_list[i]->name);
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
" bitsize=\"%d\"", reg_list[i]->size);
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
" regnum=\"%d\"", reg_list[i]->number);
|
|
if (reg_list[i]->caller_save)
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
" save-restore=\"yes\"");
|
|
else
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
" save-restore=\"no\"");
|
|
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
" type=\"%s\"", type_str);
|
|
|
|
if (reg_list[i]->group != NULL)
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
" group=\"%s\"", reg_list[i]->group);
|
|
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
"/>\n");
|
|
}
|
|
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
"</feature>\n");
|
|
|
|
current_feature++;
|
|
}
|
|
}
|
|
|
|
xml_printf(&retval, &tdesc, &pos, &size,
|
|
"</target>\n");
|
|
|
|
error:
|
|
free(features);
|
|
free(reg_list);
|
|
free(arch_defined_types);
|
|
|
|
if (retval == ERROR_OK)
|
|
*tdesc_out = tdesc;
|
|
else
|
|
free(tdesc);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_get_target_description_chunk(struct target *target, struct target_desc_format *target_desc,
|
|
char **chunk, int32_t offset, uint32_t length)
|
|
{
|
|
if (target_desc == NULL) {
|
|
LOG_ERROR("Unable to Generate Target Description");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
char *tdesc = target_desc->tdesc;
|
|
uint32_t tdesc_length = target_desc->tdesc_length;
|
|
|
|
if (tdesc == NULL) {
|
|
int retval = gdb_generate_target_description(target, &tdesc);
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("Unable to Generate Target Description");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
tdesc_length = strlen(tdesc);
|
|
}
|
|
|
|
char transfer_type;
|
|
|
|
if (length < (tdesc_length - offset))
|
|
transfer_type = 'm';
|
|
else
|
|
transfer_type = 'l';
|
|
|
|
*chunk = malloc(length + 2);
|
|
if (*chunk == NULL) {
|
|
LOG_ERROR("Unable to allocate memory");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
(*chunk)[0] = transfer_type;
|
|
if (transfer_type == 'm') {
|
|
strncpy((*chunk) + 1, tdesc + offset, length);
|
|
(*chunk)[1 + length] = '\0';
|
|
} else {
|
|
strncpy((*chunk) + 1, tdesc + offset, tdesc_length - offset);
|
|
(*chunk)[1 + (tdesc_length - offset)] = '\0';
|
|
|
|
/* After gdb-server sends out last chunk, invalidate tdesc. */
|
|
free(tdesc);
|
|
tdesc = NULL;
|
|
tdesc_length = 0;
|
|
}
|
|
|
|
target_desc->tdesc = tdesc;
|
|
target_desc->tdesc_length = tdesc_length;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_target_description_supported(struct target *target, int *supported)
|
|
{
|
|
int retval = ERROR_OK;
|
|
struct reg **reg_list = NULL;
|
|
int reg_list_size = 0;
|
|
char const **features = NULL;
|
|
int feature_list_size = 0;
|
|
|
|
retval = target_get_gdb_reg_list(target, ®_list,
|
|
®_list_size, REG_CLASS_ALL);
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("get register list failed");
|
|
goto error;
|
|
}
|
|
|
|
if (reg_list_size <= 0) {
|
|
LOG_ERROR("get register list failed");
|
|
retval = ERROR_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
/* Get a list of available target registers features */
|
|
retval = get_reg_features_list(target, &features, &feature_list_size, reg_list, reg_list_size);
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("Can't get the registers feature list");
|
|
goto error;
|
|
}
|
|
|
|
if (supported) {
|
|
if (feature_list_size)
|
|
*supported = 1;
|
|
else
|
|
*supported = 0;
|
|
}
|
|
|
|
error:
|
|
free(features);
|
|
|
|
free(reg_list);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_generate_thread_list(struct target *target, char **thread_list_out)
|
|
{
|
|
struct rtos *rtos = target->rtos;
|
|
int retval = ERROR_OK;
|
|
char *thread_list = NULL;
|
|
int pos = 0;
|
|
int size = 0;
|
|
|
|
xml_printf(&retval, &thread_list, &pos, &size,
|
|
"<?xml version=\"1.0\"?>\n"
|
|
"<threads>\n");
|
|
|
|
if (rtos != NULL) {
|
|
for (int i = 0; i < rtos->thread_count; i++) {
|
|
struct thread_detail *thread_detail = &rtos->thread_details[i];
|
|
|
|
if (!thread_detail->exists)
|
|
continue;
|
|
|
|
xml_printf(&retval, &thread_list, &pos, &size,
|
|
"<thread id=\"%" PRIx64 "\">", thread_detail->threadid);
|
|
|
|
if (thread_detail->thread_name_str != NULL)
|
|
xml_printf(&retval, &thread_list, &pos, &size,
|
|
"Name: %s", thread_detail->thread_name_str);
|
|
|
|
if (thread_detail->extra_info_str != NULL) {
|
|
if (thread_detail->thread_name_str != NULL)
|
|
xml_printf(&retval, &thread_list, &pos, &size,
|
|
", ");
|
|
xml_printf(&retval, &thread_list, &pos, &size,
|
|
thread_detail->extra_info_str);
|
|
}
|
|
|
|
xml_printf(&retval, &thread_list, &pos, &size,
|
|
"</thread>\n");
|
|
}
|
|
}
|
|
|
|
xml_printf(&retval, &thread_list, &pos, &size,
|
|
"</threads>\n");
|
|
|
|
if (retval == ERROR_OK)
|
|
*thread_list_out = thread_list;
|
|
else
|
|
free(thread_list);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int gdb_get_thread_list_chunk(struct target *target, char **thread_list,
|
|
char **chunk, int32_t offset, uint32_t length)
|
|
{
|
|
if (*thread_list == NULL) {
|
|
int retval = gdb_generate_thread_list(target, thread_list);
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("Unable to Generate Thread List");
|
|
return ERROR_FAIL;
|
|
}
|
|
}
|
|
|
|
size_t thread_list_length = strlen(*thread_list);
|
|
char transfer_type;
|
|
|
|
length = MIN(length, thread_list_length - offset);
|
|
if (length < (thread_list_length - offset))
|
|
transfer_type = 'm';
|
|
else
|
|
transfer_type = 'l';
|
|
|
|
*chunk = malloc(length + 2 + 3);
|
|
/* Allocating extra 3 bytes prevents false positive valgrind report
|
|
* of strlen(chunk) word access:
|
|
* Invalid read of size 4
|
|
* Address 0x4479934 is 44 bytes inside a block of size 45 alloc'd */
|
|
if (*chunk == NULL) {
|
|
LOG_ERROR("Unable to allocate memory");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
(*chunk)[0] = transfer_type;
|
|
strncpy((*chunk) + 1, (*thread_list) + offset, length);
|
|
(*chunk)[1 + length] = '\0';
|
|
|
|
/* After gdb-server sends out last chunk, invalidate thread list. */
|
|
if (transfer_type == 'l') {
|
|
free(*thread_list);
|
|
*thread_list = NULL;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_query_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct command_context *cmd_ctx = connection->cmd_ctx;
|
|
struct gdb_connection *gdb_connection = connection->priv;
|
|
struct target *target = get_target_from_connection(connection);
|
|
|
|
if (strncmp(packet, "qRcmd,", 6) == 0) {
|
|
if (packet_size > 6) {
|
|
char *cmd;
|
|
cmd = malloc((packet_size - 6) / 2 + 1);
|
|
size_t len = unhexify((uint8_t *)cmd, packet + 6, (packet_size - 6) / 2);
|
|
cmd[len] = 0;
|
|
|
|
/* We want to print all debug output to GDB connection */
|
|
log_add_callback(gdb_log_callback, connection);
|
|
target_call_timer_callbacks_now();
|
|
/* some commands need to know the GDB connection, make note of current
|
|
* GDB connection. */
|
|
current_gdb_connection = gdb_connection;
|
|
command_run_line(cmd_ctx, cmd);
|
|
current_gdb_connection = NULL;
|
|
target_call_timer_callbacks_now();
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
free(cmd);
|
|
}
|
|
gdb_put_packet(connection, "OK", 2);
|
|
return ERROR_OK;
|
|
} else if (strncmp(packet, "qCRC:", 5) == 0) {
|
|
if (packet_size > 5) {
|
|
int retval;
|
|
char gdb_reply[10];
|
|
char *separator;
|
|
uint32_t checksum;
|
|
target_addr_t addr = 0;
|
|
uint32_t len = 0;
|
|
|
|
/* skip command character */
|
|
packet += 5;
|
|
|
|
addr = strtoull(packet, &separator, 16);
|
|
|
|
if (*separator != ',') {
|
|
LOG_ERROR("incomplete read memory packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
len = strtoul(separator + 1, NULL, 16);
|
|
|
|
retval = target_checksum_memory(target, addr, len, &checksum);
|
|
|
|
if (retval == ERROR_OK) {
|
|
snprintf(gdb_reply, 10, "C%8.8" PRIx32 "", checksum);
|
|
gdb_put_packet(connection, gdb_reply, 9);
|
|
} else {
|
|
retval = gdb_error(connection, retval);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
} else if (strncmp(packet, "qSupported", 10) == 0) {
|
|
/* we currently support packet size and qXfer:memory-map:read (if enabled)
|
|
* qXfer:features:read is supported for some targets */
|
|
int retval = ERROR_OK;
|
|
char *buffer = NULL;
|
|
int pos = 0;
|
|
int size = 0;
|
|
int gdb_target_desc_supported = 0;
|
|
|
|
/* we need to test that the target supports target descriptions */
|
|
retval = gdb_target_description_supported(target, &gdb_target_desc_supported);
|
|
if (retval != ERROR_OK) {
|
|
LOG_INFO("Failed detecting Target Description Support, disabling");
|
|
gdb_target_desc_supported = 0;
|
|
}
|
|
|
|
/* support may be disabled globally */
|
|
if (gdb_use_target_description == 0) {
|
|
if (gdb_target_desc_supported)
|
|
LOG_WARNING("Target Descriptions Supported, but disabled");
|
|
gdb_target_desc_supported = 0;
|
|
}
|
|
|
|
xml_printf(&retval,
|
|
&buffer,
|
|
&pos,
|
|
&size,
|
|
"PacketSize=%x;qXfer:memory-map:read%c;qXfer:features:read%c;qXfer:threads:read+;QStartNoAckMode+;vContSupported+",
|
|
(GDB_BUFFER_SIZE - 1),
|
|
((gdb_use_memory_map == 1) && (flash_get_bank_count() > 0)) ? '+' : '-',
|
|
(gdb_target_desc_supported == 1) ? '+' : '-');
|
|
|
|
if (retval != ERROR_OK) {
|
|
gdb_send_error(connection, 01);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
gdb_put_packet(connection, buffer, strlen(buffer));
|
|
free(buffer);
|
|
|
|
return ERROR_OK;
|
|
} else if ((strncmp(packet, "qXfer:memory-map:read::", 23) == 0)
|
|
&& (flash_get_bank_count() > 0))
|
|
return gdb_memory_map(connection, packet, packet_size);
|
|
else if (strncmp(packet, "qXfer:features:read:", 20) == 0) {
|
|
char *xml = NULL;
|
|
int retval = ERROR_OK;
|
|
|
|
int offset;
|
|
unsigned int length;
|
|
|
|
/* skip command character */
|
|
packet += 20;
|
|
|
|
if (decode_xfer_read(packet, NULL, &offset, &length) < 0) {
|
|
gdb_send_error(connection, 01);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* Target should prepare correct target description for annex.
|
|
* The first character of returned xml is 'm' or 'l'. 'm' for
|
|
* there are *more* chunks to transfer. 'l' for it is the *last*
|
|
* chunk of target description.
|
|
*/
|
|
retval = gdb_get_target_description_chunk(target, &gdb_connection->target_desc,
|
|
&xml, offset, length);
|
|
if (retval != ERROR_OK) {
|
|
gdb_error(connection, retval);
|
|
return retval;
|
|
}
|
|
|
|
gdb_put_packet(connection, xml, strlen(xml));
|
|
|
|
free(xml);
|
|
return ERROR_OK;
|
|
} else if (strncmp(packet, "qXfer:threads:read:", 19) == 0) {
|
|
char *xml = NULL;
|
|
int retval = ERROR_OK;
|
|
|
|
int offset;
|
|
unsigned int length;
|
|
|
|
/* skip command character */
|
|
packet += 19;
|
|
|
|
if (decode_xfer_read(packet, NULL, &offset, &length) < 0) {
|
|
gdb_send_error(connection, 01);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* Target should prepare correct thread list for annex.
|
|
* The first character of returned xml is 'm' or 'l'. 'm' for
|
|
* there are *more* chunks to transfer. 'l' for it is the *last*
|
|
* chunk of target description.
|
|
*/
|
|
retval = gdb_get_thread_list_chunk(target, &gdb_connection->thread_list,
|
|
&xml, offset, length);
|
|
if (retval != ERROR_OK) {
|
|
gdb_error(connection, retval);
|
|
return retval;
|
|
}
|
|
|
|
gdb_put_packet(connection, xml, strlen(xml));
|
|
|
|
free(xml);
|
|
return ERROR_OK;
|
|
} else if (strncmp(packet, "QStartNoAckMode", 15) == 0) {
|
|
gdb_connection->noack_mode = 1;
|
|
gdb_put_packet(connection, "OK", 2);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
gdb_put_packet(connection, "", 0);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static bool gdb_handle_vcont_packet(struct connection *connection, const char *packet, int packet_size)
|
|
{
|
|
struct gdb_connection *gdb_connection = connection->priv;
|
|
struct target *target = get_target_from_connection(connection);
|
|
const char *parse = packet;
|
|
int retval;
|
|
|
|
/* query for vCont supported */
|
|
if (parse[0] == '?') {
|
|
if (target->type->step != NULL) {
|
|
/* gdb doesn't accept c without C and s without S */
|
|
gdb_put_packet(connection, "vCont;c;C;s;S", 13);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (parse[0] == ';') {
|
|
++parse;
|
|
--packet_size;
|
|
}
|
|
|
|
/* simple case, a continue packet */
|
|
if (parse[0] == 'c') {
|
|
gdb_running_type = 'c';
|
|
LOG_DEBUG("target %s continue", target_name(target));
|
|
log_add_callback(gdb_log_callback, connection);
|
|
retval = target_resume(target, 1, 0, 0, 0);
|
|
if (retval == ERROR_TARGET_NOT_HALTED)
|
|
LOG_INFO("target %s was not halted when resume was requested", target_name(target));
|
|
|
|
/* poll target in an attempt to make its internal state consistent */
|
|
if (retval != ERROR_OK) {
|
|
retval = target_poll(target);
|
|
if (retval != ERROR_OK)
|
|
LOG_DEBUG("error polling target %s after failed resume", target_name(target));
|
|
}
|
|
|
|
/*
|
|
* We don't report errors to gdb here, move frontend_state to
|
|
* TARGET_RUNNING to stay in sync with gdb's expectation of the
|
|
* target state
|
|
*/
|
|
gdb_connection->frontend_state = TARGET_RUNNING;
|
|
target_call_event_callbacks(target, TARGET_EVENT_GDB_START);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* single-step or step-over-breakpoint */
|
|
if (parse[0] == 's') {
|
|
gdb_running_type = 's';
|
|
bool fake_step = false;
|
|
|
|
if (strncmp(parse, "s:", 2) == 0) {
|
|
struct target *ct = target;
|
|
int current_pc = 1;
|
|
int64_t thread_id;
|
|
char *endp;
|
|
|
|
parse += 2;
|
|
packet_size -= 2;
|
|
|
|
thread_id = strtoll(parse, &endp, 16);
|
|
if (endp != NULL) {
|
|
packet_size -= endp - parse;
|
|
parse = endp;
|
|
}
|
|
|
|
if (target->rtos != NULL) {
|
|
/* FIXME: why is this necessary? rtos state should be up-to-date here already! */
|
|
rtos_update_threads(target);
|
|
|
|
target->rtos->gdb_target_for_threadid(connection, thread_id, &ct);
|
|
|
|
/*
|
|
* check if the thread to be stepped is the current rtos thread
|
|
* if not, we must fake the step
|
|
*/
|
|
if (target->rtos->current_thread != thread_id)
|
|
fake_step = true;
|
|
}
|
|
|
|
if (parse[0] == ';') {
|
|
++parse;
|
|
--packet_size;
|
|
|
|
if (parse[0] == 'c') {
|
|
parse += 1;
|
|
packet_size -= 1;
|
|
|
|
/* check if thread-id follows */
|
|
if (parse[0] == ':') {
|
|
int64_t tid;
|
|
parse += 1;
|
|
packet_size -= 1;
|
|
|
|
tid = strtoll(parse, &endp, 16);
|
|
if (tid == thread_id) {
|
|
/*
|
|
* Special case: only step a single thread (core),
|
|
* keep the other threads halted. Currently, only
|
|
* aarch64 target understands it. Other target types don't
|
|
* care (nobody checks the actual value of 'current')
|
|
* and it doesn't really matter. This deserves
|
|
* a symbolic constant and a formal interface documentation
|
|
* at a later time.
|
|
*/
|
|
LOG_DEBUG("request to step current core only");
|
|
/* uncomment after checking that indeed other targets are safe */
|
|
/*current_pc = 2;*/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG_DEBUG("target %s single-step thread %"PRIx64, target_name(ct), thread_id);
|
|
log_add_callback(gdb_log_callback, connection);
|
|
target_call_event_callbacks(ct, TARGET_EVENT_GDB_START);
|
|
|
|
/*
|
|
* work around an annoying gdb behaviour: when the current thread
|
|
* is changed in gdb, it assumes that the target can follow and also
|
|
* make the thread current. This is an assumption that cannot hold
|
|
* for a real target running a multi-threading OS. We just fake
|
|
* the step to not trigger an internal error in gdb. See
|
|
* https://sourceware.org/bugzilla/show_bug.cgi?id=22925 for details
|
|
*/
|
|
if (fake_step) {
|
|
int sig_reply_len;
|
|
char sig_reply[128];
|
|
|
|
LOG_DEBUG("fake step thread %"PRIx64, thread_id);
|
|
|
|
sig_reply_len = snprintf(sig_reply, sizeof(sig_reply),
|
|
"T05thread:%016"PRIx64";", thread_id);
|
|
|
|
gdb_put_packet(connection, sig_reply, sig_reply_len);
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* support for gdb_sync command */
|
|
if (gdb_connection->sync) {
|
|
gdb_connection->sync = false;
|
|
if (ct->state == TARGET_HALTED) {
|
|
LOG_WARNING("stepi ignored. GDB will now fetch the register state " \
|
|
"from the target.");
|
|
gdb_sig_halted(connection);
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
} else
|
|
gdb_connection->frontend_state = TARGET_RUNNING;
|
|
return true;
|
|
}
|
|
|
|
retval = target_step(ct, current_pc, 0, 0);
|
|
if (retval == ERROR_TARGET_NOT_HALTED)
|
|
LOG_INFO("target %s was not halted when step was requested", target_name(ct));
|
|
|
|
/* if step was successful send a reply back to gdb */
|
|
if (retval == ERROR_OK) {
|
|
retval = target_poll(ct);
|
|
if (retval != ERROR_OK)
|
|
LOG_DEBUG("error polling target %s after successful step", target_name(ct));
|
|
/* send back signal information */
|
|
gdb_signal_reply(ct, connection);
|
|
/* stop forwarding log packets! */
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
} else
|
|
gdb_connection->frontend_state = TARGET_RUNNING;
|
|
} else {
|
|
LOG_ERROR("Unknown vCont packet");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int gdb_v_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct gdb_connection *gdb_connection = connection->priv;
|
|
int result;
|
|
|
|
struct target *target = get_target_from_connection(connection);
|
|
if (target->rtos != NULL && target->rtos->gdb_v_packet != NULL) {
|
|
int out = target->rtos->gdb_v_packet(connection, packet, packet_size);
|
|
if (out != GDB_THREAD_PACKET_NOT_CONSUMED)
|
|
return out;
|
|
}
|
|
|
|
if (strncmp(packet, "vCont", 5) == 0) {
|
|
bool handled;
|
|
|
|
packet += 5;
|
|
packet_size -= 5;
|
|
|
|
handled = gdb_handle_vcont_packet(connection, packet, packet_size);
|
|
if (!handled)
|
|
gdb_put_packet(connection, "", 0);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* if flash programming disabled - send a empty reply */
|
|
|
|
if (gdb_flash_program == 0) {
|
|
gdb_put_packet(connection, "", 0);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
if (strncmp(packet, "vFlashErase:", 12) == 0) {
|
|
unsigned long addr;
|
|
unsigned long length;
|
|
|
|
char const *parse = packet + 12;
|
|
if (*parse == '\0') {
|
|
LOG_ERROR("incomplete vFlashErase packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
addr = strtoul(parse, (char **)&parse, 16);
|
|
|
|
if (*(parse++) != ',' || *parse == '\0') {
|
|
LOG_ERROR("incomplete vFlashErase packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
length = strtoul(parse, (char **)&parse, 16);
|
|
|
|
if (*parse != '\0') {
|
|
LOG_ERROR("incomplete vFlashErase packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
|
|
/* assume all sectors need erasing - stops any problems
|
|
* when flash_write is called multiple times */
|
|
flash_set_dirty();
|
|
|
|
/* perform any target specific operations before the erase */
|
|
target_call_event_callbacks(target,
|
|
TARGET_EVENT_GDB_FLASH_ERASE_START);
|
|
|
|
/* vFlashErase:addr,length messages require region start and
|
|
* end to be "block" aligned ... if padding is ever needed,
|
|
* GDB will have become dangerously confused.
|
|
*/
|
|
result = flash_erase_address_range(target, false, addr,
|
|
length);
|
|
|
|
/* perform any target specific operations after the erase */
|
|
target_call_event_callbacks(target,
|
|
TARGET_EVENT_GDB_FLASH_ERASE_END);
|
|
|
|
/* perform erase */
|
|
if (result != ERROR_OK) {
|
|
/* GDB doesn't evaluate the actual error number returned,
|
|
* treat a failed erase as an I/O error
|
|
*/
|
|
gdb_send_error(connection, EIO);
|
|
LOG_ERROR("flash_erase returned %i", result);
|
|
} else
|
|
gdb_put_packet(connection, "OK", 2);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
if (strncmp(packet, "vFlashWrite:", 12) == 0) {
|
|
int retval;
|
|
unsigned long addr;
|
|
unsigned long length;
|
|
char const *parse = packet + 12;
|
|
|
|
if (*parse == '\0') {
|
|
LOG_ERROR("incomplete vFlashErase packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
addr = strtoul(parse, (char **)&parse, 16);
|
|
if (*(parse++) != ':') {
|
|
LOG_ERROR("incomplete vFlashErase packet received, dropping connection");
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
}
|
|
length = packet_size - (parse - packet);
|
|
|
|
/* create a new image if there isn't already one */
|
|
if (gdb_connection->vflash_image == NULL) {
|
|
gdb_connection->vflash_image = malloc(sizeof(struct image));
|
|
image_open(gdb_connection->vflash_image, "", "build");
|
|
}
|
|
|
|
/* create new section with content from packet buffer */
|
|
retval = image_add_section(gdb_connection->vflash_image,
|
|
addr, length, 0x0, (uint8_t const *)parse);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
|
|
gdb_put_packet(connection, "OK", 2);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
if (strncmp(packet, "vFlashDone", 10) == 0) {
|
|
uint32_t written;
|
|
|
|
/* process the flashing buffer. No need to erase as GDB
|
|
* always issues a vFlashErase first. */
|
|
target_call_event_callbacks(target,
|
|
TARGET_EVENT_GDB_FLASH_WRITE_START);
|
|
result = flash_write(target, gdb_connection->vflash_image,
|
|
&written, 0);
|
|
target_call_event_callbacks(target,
|
|
TARGET_EVENT_GDB_FLASH_WRITE_END);
|
|
if (result != ERROR_OK) {
|
|
if (result == ERROR_FLASH_DST_OUT_OF_BANK)
|
|
gdb_put_packet(connection, "E.memtype", 9);
|
|
else
|
|
gdb_send_error(connection, EIO);
|
|
} else {
|
|
LOG_DEBUG("wrote %u bytes from vFlash image to flash", (unsigned)written);
|
|
gdb_put_packet(connection, "OK", 2);
|
|
}
|
|
|
|
image_close(gdb_connection->vflash_image);
|
|
free(gdb_connection->vflash_image);
|
|
gdb_connection->vflash_image = NULL;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
gdb_put_packet(connection, "", 0);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_detach(struct connection *connection)
|
|
{
|
|
/*
|
|
* Only reply "OK" to GDB
|
|
* it will close the connection and this will trigger a call to
|
|
* gdb_connection_closed() that will in turn trigger the event
|
|
* TARGET_EVENT_GDB_DETACH
|
|
*/
|
|
return gdb_put_packet(connection, "OK", 2);
|
|
}
|
|
|
|
/* The format of 'F' response packet is
|
|
* Fretcode,errno,Ctrl-C flag;call-specific attachment
|
|
*/
|
|
static int gdb_fileio_response_packet(struct connection *connection,
|
|
char const *packet, int packet_size)
|
|
{
|
|
struct target *target = get_target_from_connection(connection);
|
|
char *separator;
|
|
char *parsing_point;
|
|
int fileio_retcode = strtoul(packet + 1, &separator, 16);
|
|
int fileio_errno = 0;
|
|
bool fileio_ctrl_c = false;
|
|
int retval;
|
|
|
|
LOG_DEBUG("-");
|
|
|
|
if (*separator == ',') {
|
|
parsing_point = separator + 1;
|
|
fileio_errno = strtoul(parsing_point, &separator, 16);
|
|
if (*separator == ',') {
|
|
if (*(separator + 1) == 'C') {
|
|
/* TODO: process ctrl-c */
|
|
fileio_ctrl_c = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG_DEBUG("File-I/O response, retcode: 0x%x, errno: 0x%x, ctrl-c: %s",
|
|
fileio_retcode, fileio_errno, fileio_ctrl_c ? "true" : "false");
|
|
|
|
retval = target_gdb_fileio_end(target, fileio_retcode, fileio_errno, fileio_ctrl_c);
|
|
if (retval != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
/* After File-I/O ends, keep continue or step */
|
|
if (gdb_running_type == 'c')
|
|
retval = target_resume(target, 1, 0x0, 0, 0);
|
|
else if (gdb_running_type == 's')
|
|
retval = target_step(target, 1, 0x0, 0);
|
|
else
|
|
retval = ERROR_FAIL;
|
|
|
|
if (retval != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static void gdb_log_callback(void *priv, const char *file, unsigned line,
|
|
const char *function, const char *string)
|
|
{
|
|
struct connection *connection = priv;
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
|
|
if (gdb_con->busy) {
|
|
/* do not reply this using the O packet */
|
|
return;
|
|
}
|
|
|
|
gdb_output_con(connection, string);
|
|
}
|
|
|
|
static void gdb_sig_halted(struct connection *connection)
|
|
{
|
|
char sig_reply[4];
|
|
snprintf(sig_reply, 4, "T%2.2x", 2);
|
|
gdb_put_packet(connection, sig_reply, 3);
|
|
}
|
|
|
|
static int gdb_input_inner(struct connection *connection)
|
|
{
|
|
/* Do not allocate this on the stack */
|
|
static char gdb_packet_buffer[GDB_BUFFER_SIZE];
|
|
|
|
struct target *target;
|
|
char const *packet = gdb_packet_buffer;
|
|
int packet_size;
|
|
int retval;
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
static int extended_protocol;
|
|
|
|
target = get_target_from_connection(connection);
|
|
|
|
/* drain input buffer. If one of the packets fail, then an error
|
|
* packet is replied, if applicable.
|
|
*
|
|
* This loop will terminate and the error code is returned.
|
|
*
|
|
* The calling fn will check if this error is something that
|
|
* can be recovered from, or if the connection must be closed.
|
|
*
|
|
* If the error is recoverable, this fn is called again to
|
|
* drain the rest of the buffer.
|
|
*/
|
|
do {
|
|
packet_size = GDB_BUFFER_SIZE-1;
|
|
retval = gdb_get_packet(connection, gdb_packet_buffer, &packet_size);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
|
|
/* terminate with zero */
|
|
gdb_packet_buffer[packet_size] = '\0';
|
|
|
|
if (LOG_LEVEL_IS(LOG_LVL_DEBUG)) {
|
|
if (packet[0] == 'X') {
|
|
/* binary packets spew junk into the debug log stream */
|
|
char buf[50];
|
|
int x;
|
|
for (x = 0; (x < 49) && (packet[x] != ':'); x++)
|
|
buf[x] = packet[x];
|
|
buf[x] = 0;
|
|
LOG_DEBUG("received packet: '%s:<binary-data>'", buf);
|
|
} else
|
|
LOG_DEBUG("received packet: '%s'", packet);
|
|
}
|
|
|
|
if (packet_size > 0) {
|
|
retval = ERROR_OK;
|
|
switch (packet[0]) {
|
|
case 'T': /* Is thread alive? */
|
|
gdb_thread_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'H': /* Set current thread ( 'c' for step and continue,
|
|
* 'g' for all other operations ) */
|
|
gdb_thread_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'q':
|
|
case 'Q':
|
|
retval = gdb_thread_packet(connection, packet, packet_size);
|
|
if (retval == GDB_THREAD_PACKET_NOT_CONSUMED)
|
|
retval = gdb_query_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'g':
|
|
retval = gdb_get_registers_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'G':
|
|
retval = gdb_set_registers_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'p':
|
|
retval = gdb_get_register_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'P':
|
|
retval = gdb_set_register_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'm':
|
|
retval = gdb_read_memory_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'M':
|
|
retval = gdb_write_memory_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'z':
|
|
case 'Z':
|
|
retval = gdb_breakpoint_watchpoint_packet(connection, packet, packet_size);
|
|
break;
|
|
case '?':
|
|
gdb_last_signal_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'c':
|
|
case 's':
|
|
{
|
|
gdb_thread_packet(connection, packet, packet_size);
|
|
log_add_callback(gdb_log_callback, connection);
|
|
|
|
if (gdb_con->mem_write_error) {
|
|
LOG_ERROR("Memory write failure!");
|
|
|
|
/* now that we have reported the memory write error,
|
|
* we can clear the condition */
|
|
gdb_con->mem_write_error = false;
|
|
}
|
|
|
|
bool nostep = false;
|
|
bool already_running = false;
|
|
if (target->state == TARGET_RUNNING) {
|
|
LOG_WARNING("WARNING! The target is already running. "
|
|
"All changes GDB did to registers will be discarded! "
|
|
"Waiting for target to halt.");
|
|
already_running = true;
|
|
} else if (target->state != TARGET_HALTED) {
|
|
LOG_WARNING("The target is not in the halted nor running stated, " \
|
|
"stepi/continue ignored.");
|
|
nostep = true;
|
|
} else if ((packet[0] == 's') && gdb_con->sync) {
|
|
/* Hmm..... when you issue a continue in GDB, then a "stepi" is
|
|
* sent by GDB first to OpenOCD, thus defeating the check to
|
|
* make only the single stepping have the sync feature...
|
|
*/
|
|
nostep = true;
|
|
LOG_WARNING("stepi ignored. GDB will now fetch the register state " \
|
|
"from the target.");
|
|
}
|
|
gdb_con->sync = false;
|
|
|
|
if (!already_running && nostep) {
|
|
/* Either the target isn't in the halted state, then we can't
|
|
* step/continue. This might be early setup, etc.
|
|
*
|
|
* Or we want to allow GDB to pick up a fresh set of
|
|
* register values without modifying the target state.
|
|
*
|
|
*/
|
|
gdb_sig_halted(connection);
|
|
|
|
/* stop forwarding log packets! */
|
|
log_remove_callback(gdb_log_callback, connection);
|
|
} else {
|
|
/* We're running/stepping, in which case we can
|
|
* forward log output until the target is halted
|
|
*/
|
|
gdb_con->frontend_state = TARGET_RUNNING;
|
|
target_call_event_callbacks(target, TARGET_EVENT_GDB_START);
|
|
|
|
if (!already_running) {
|
|
/* Here we don't want packet processing to stop even if this fails,
|
|
* so we use a local variable instead of retval. */
|
|
retval = gdb_step_continue_packet(connection, packet, packet_size);
|
|
if (retval != ERROR_OK) {
|
|
/* we'll never receive a halted
|
|
* condition... issue a false one..
|
|
*/
|
|
gdb_frontend_halted(target, connection);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'v':
|
|
retval = gdb_v_packet(connection, packet, packet_size);
|
|
break;
|
|
case 'D':
|
|
retval = gdb_detach(connection);
|
|
extended_protocol = 0;
|
|
break;
|
|
case 'X':
|
|
retval = gdb_write_memory_binary_packet(connection, packet, packet_size);
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
break;
|
|
case 'k':
|
|
if (extended_protocol != 0) {
|
|
gdb_con->attached = false;
|
|
break;
|
|
}
|
|
gdb_put_packet(connection, "OK", 2);
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
case '!':
|
|
/* handle extended remote protocol */
|
|
extended_protocol = 1;
|
|
gdb_put_packet(connection, "OK", 2);
|
|
break;
|
|
case 'R':
|
|
/* handle extended restart packet */
|
|
breakpoint_clear_target(target);
|
|
watchpoint_clear_target(target);
|
|
command_run_linef(connection->cmd_ctx, "ocd_gdb_restart %s",
|
|
target_name(target));
|
|
/* set connection as attached after reset */
|
|
gdb_con->attached = true;
|
|
/* info rtos parts */
|
|
gdb_thread_packet(connection, packet, packet_size);
|
|
break;
|
|
|
|
case 'j':
|
|
/* packet supported only by smp target i.e cortex_a.c*/
|
|
/* handle smp packet replying coreid played to gbd */
|
|
gdb_read_smp_packet(connection, packet, packet_size);
|
|
break;
|
|
|
|
case 'J':
|
|
/* packet supported only by smp target i.e cortex_a.c */
|
|
/* handle smp packet setting coreid to be played at next
|
|
* resume to gdb */
|
|
gdb_write_smp_packet(connection, packet, packet_size);
|
|
break;
|
|
|
|
case 'F':
|
|
/* File-I/O extension */
|
|
/* After gdb uses host-side syscall to complete target file
|
|
* I/O, gdb sends host-side syscall return value to target
|
|
* by 'F' packet.
|
|
* The format of 'F' response packet is
|
|
* Fretcode,errno,Ctrl-C flag;call-specific attachment
|
|
*/
|
|
gdb_con->frontend_state = TARGET_RUNNING;
|
|
log_add_callback(gdb_log_callback, connection);
|
|
gdb_fileio_response_packet(connection, packet, packet_size);
|
|
break;
|
|
|
|
default:
|
|
/* ignore unknown packets */
|
|
LOG_DEBUG("ignoring 0x%2.2x packet", packet[0]);
|
|
gdb_put_packet(connection, NULL, 0);
|
|
break;
|
|
}
|
|
|
|
/* if a packet handler returned an error, exit input loop */
|
|
if (retval != ERROR_OK)
|
|
return retval;
|
|
}
|
|
|
|
if (gdb_con->ctrl_c) {
|
|
if (target->state == TARGET_RUNNING) {
|
|
struct target *t = target;
|
|
if (target->rtos)
|
|
target->rtos->gdb_target_for_threadid(connection, target->rtos->current_threadid, &t);
|
|
retval = target_halt(t);
|
|
if (retval == ERROR_OK)
|
|
retval = target_poll(t);
|
|
if (retval != ERROR_OK)
|
|
target_call_event_callbacks(target, TARGET_EVENT_GDB_HALT);
|
|
gdb_con->ctrl_c = 0;
|
|
} else {
|
|
LOG_INFO("The target is not running when halt was requested, stopping GDB.");
|
|
target_call_event_callbacks(target, TARGET_EVENT_GDB_HALT);
|
|
}
|
|
}
|
|
|
|
} while (gdb_con->buf_cnt > 0);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_input(struct connection *connection)
|
|
{
|
|
int retval = gdb_input_inner(connection);
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
if (retval == ERROR_SERVER_REMOTE_CLOSED)
|
|
return retval;
|
|
|
|
/* logging does not propagate the error, yet can set the gdb_con->closed flag */
|
|
if (gdb_con->closed)
|
|
return ERROR_SERVER_REMOTE_CLOSED;
|
|
|
|
/* we'll recover from any other errors(e.g. temporary timeouts, etc.) */
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int gdb_target_start(struct target *target, const char *port)
|
|
{
|
|
struct gdb_service *gdb_service;
|
|
int ret;
|
|
gdb_service = malloc(sizeof(struct gdb_service));
|
|
|
|
if (NULL == gdb_service)
|
|
return -ENOMEM;
|
|
|
|
gdb_service->target = target;
|
|
gdb_service->core[0] = -1;
|
|
gdb_service->core[1] = -1;
|
|
target->gdb_service = gdb_service;
|
|
|
|
ret = add_service("gdb",
|
|
port, 1, &gdb_new_connection, &gdb_input,
|
|
&gdb_connection_closed, gdb_service);
|
|
/* initialialize all targets gdb service with the same pointer */
|
|
{
|
|
struct target_list *head;
|
|
struct target *curr;
|
|
head = target->head;
|
|
while (head != (struct target_list *)NULL) {
|
|
curr = head->target;
|
|
if (curr != target)
|
|
curr->gdb_service = gdb_service;
|
|
head = head->next;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int gdb_target_add_one(struct target *target)
|
|
{
|
|
if (strcmp(gdb_port, "disabled") == 0) {
|
|
LOG_INFO("gdb port disabled");
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* one gdb instance per smp list */
|
|
if ((target->smp) && (target->gdb_service))
|
|
return ERROR_OK;
|
|
int retval = gdb_target_start(target, gdb_port_next);
|
|
if (retval == ERROR_OK) {
|
|
long portnumber;
|
|
/* If we can parse the port number
|
|
* then we increment the port number for the next target.
|
|
*/
|
|
char *end;
|
|
portnumber = strtol(gdb_port_next, &end, 0);
|
|
if (!*end) {
|
|
if (parse_long(gdb_port_next, &portnumber) == ERROR_OK) {
|
|
free(gdb_port_next);
|
|
if (portnumber) {
|
|
gdb_port_next = alloc_printf("%d", portnumber+1);
|
|
} else {
|
|
/* Don't increment if gdb_port is 0, since we're just
|
|
* trying to allocate an unused port. */
|
|
gdb_port_next = strdup("0");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
int gdb_target_add_all(struct target *target)
|
|
{
|
|
if (strcmp(gdb_port, "disabled") == 0) {
|
|
LOG_INFO("gdb server disabled");
|
|
return ERROR_OK;
|
|
}
|
|
|
|
if (NULL == target) {
|
|
LOG_WARNING("gdb services need one or more targets defined");
|
|
return ERROR_OK;
|
|
}
|
|
|
|
while (NULL != target) {
|
|
int retval = gdb_target_add_one(target);
|
|
if (ERROR_OK != retval)
|
|
return retval;
|
|
|
|
target = target->next;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
COMMAND_HANDLER(handle_gdb_sync_command)
|
|
{
|
|
if (CMD_ARGC != 0)
|
|
return ERROR_COMMAND_SYNTAX_ERROR;
|
|
|
|
if (current_gdb_connection == NULL) {
|
|
command_print(CMD_CTX,
|
|
"gdb_sync command can only be run from within gdb using \"monitor gdb_sync\"");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
current_gdb_connection->sync = true;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* daemon configuration command gdb_port */
|
|
COMMAND_HANDLER(handle_gdb_port_command)
|
|
{
|
|
int retval = CALL_COMMAND_HANDLER(server_pipe_command, &gdb_port);
|
|
if (ERROR_OK == retval) {
|
|
free(gdb_port_next);
|
|
gdb_port_next = strdup(gdb_port);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
COMMAND_HANDLER(handle_gdb_memory_map_command)
|
|
{
|
|
if (CMD_ARGC != 1)
|
|
return ERROR_COMMAND_SYNTAX_ERROR;
|
|
|
|
COMMAND_PARSE_ENABLE(CMD_ARGV[0], gdb_use_memory_map);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
COMMAND_HANDLER(handle_gdb_flash_program_command)
|
|
{
|
|
if (CMD_ARGC != 1)
|
|
return ERROR_COMMAND_SYNTAX_ERROR;
|
|
|
|
COMMAND_PARSE_ENABLE(CMD_ARGV[0], gdb_flash_program);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
COMMAND_HANDLER(handle_gdb_report_data_abort_command)
|
|
{
|
|
if (CMD_ARGC != 1)
|
|
return ERROR_COMMAND_SYNTAX_ERROR;
|
|
|
|
COMMAND_PARSE_ENABLE(CMD_ARGV[0], gdb_report_data_abort);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
COMMAND_HANDLER(handle_gdb_report_register_access_error)
|
|
{
|
|
if (CMD_ARGC != 1)
|
|
return ERROR_COMMAND_SYNTAX_ERROR;
|
|
|
|
COMMAND_PARSE_ENABLE(CMD_ARGV[0], gdb_report_register_access_error);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/* gdb_breakpoint_override */
|
|
COMMAND_HANDLER(handle_gdb_breakpoint_override_command)
|
|
{
|
|
if (CMD_ARGC == 0) {
|
|
/* nothing */
|
|
} else if (CMD_ARGC == 1) {
|
|
gdb_breakpoint_override = 1;
|
|
if (strcmp(CMD_ARGV[0], "hard") == 0)
|
|
gdb_breakpoint_override_type = BKPT_HARD;
|
|
else if (strcmp(CMD_ARGV[0], "soft") == 0)
|
|
gdb_breakpoint_override_type = BKPT_SOFT;
|
|
else if (strcmp(CMD_ARGV[0], "disable") == 0)
|
|
gdb_breakpoint_override = 0;
|
|
} else
|
|
return ERROR_COMMAND_SYNTAX_ERROR;
|
|
if (gdb_breakpoint_override)
|
|
LOG_USER("force %s breakpoints",
|
|
(gdb_breakpoint_override_type == BKPT_HARD) ? "hard" : "soft");
|
|
else
|
|
LOG_USER("breakpoint type is not overridden");
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
COMMAND_HANDLER(handle_gdb_target_description_command)
|
|
{
|
|
if (CMD_ARGC != 1)
|
|
return ERROR_COMMAND_SYNTAX_ERROR;
|
|
|
|
COMMAND_PARSE_ENABLE(CMD_ARGV[0], gdb_use_target_description);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
COMMAND_HANDLER(handle_gdb_save_tdesc_command)
|
|
{
|
|
char *tdesc;
|
|
uint32_t tdesc_length;
|
|
struct target *target = get_current_target(CMD_CTX);
|
|
|
|
int retval = gdb_generate_target_description(target, &tdesc);
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("Unable to Generate Target Description");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
tdesc_length = strlen(tdesc);
|
|
|
|
struct fileio *fileio;
|
|
size_t size_written;
|
|
|
|
char *tdesc_filename = alloc_printf("%s.xml", target_type_name(target));
|
|
if (tdesc_filename == NULL) {
|
|
retval = ERROR_FAIL;
|
|
goto out;
|
|
}
|
|
|
|
retval = fileio_open(&fileio, tdesc_filename, FILEIO_WRITE, FILEIO_TEXT);
|
|
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("Can't open %s for writing", tdesc_filename);
|
|
goto out;
|
|
}
|
|
|
|
retval = fileio_write(fileio, tdesc_length, tdesc, &size_written);
|
|
|
|
fileio_close(fileio);
|
|
|
|
if (retval != ERROR_OK)
|
|
LOG_ERROR("Error while writing the tdesc file");
|
|
|
|
out:
|
|
free(tdesc_filename);
|
|
free(tdesc);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static const struct command_registration gdb_command_handlers[] = {
|
|
{
|
|
.name = "gdb_sync",
|
|
.handler = handle_gdb_sync_command,
|
|
.mode = COMMAND_ANY,
|
|
.help = "next stepi will return immediately allowing "
|
|
"GDB to fetch register state without affecting "
|
|
"target state",
|
|
.usage = ""
|
|
},
|
|
{
|
|
.name = "gdb_port",
|
|
.handler = handle_gdb_port_command,
|
|
.mode = COMMAND_ANY,
|
|
.help = "Normally gdb listens to a TCP/IP port. Each subsequent GDB "
|
|
"server listens for the next port number after the "
|
|
"base port number specified. "
|
|
"No arguments reports GDB port. \"pipe\" means listen to stdin "
|
|
"output to stdout, an integer is base port number, \"disabled\" disables "
|
|
"port. Any other string is are interpreted as named pipe to listen to. "
|
|
"Output pipe is the same name as input pipe, but with 'o' appended.",
|
|
.usage = "[port_num]",
|
|
},
|
|
{
|
|
.name = "gdb_memory_map",
|
|
.handler = handle_gdb_memory_map_command,
|
|
.mode = COMMAND_CONFIG,
|
|
.help = "enable or disable memory map",
|
|
.usage = "('enable'|'disable')"
|
|
},
|
|
{
|
|
.name = "gdb_flash_program",
|
|
.handler = handle_gdb_flash_program_command,
|
|
.mode = COMMAND_CONFIG,
|
|
.help = "enable or disable flash program",
|
|
.usage = "('enable'|'disable')"
|
|
},
|
|
{
|
|
.name = "gdb_report_data_abort",
|
|
.handler = handle_gdb_report_data_abort_command,
|
|
.mode = COMMAND_CONFIG,
|
|
.help = "enable or disable reporting data aborts",
|
|
.usage = "('enable'|'disable')"
|
|
},
|
|
{
|
|
.name = "gdb_report_register_access_error",
|
|
.handler = handle_gdb_report_register_access_error,
|
|
.mode = COMMAND_CONFIG,
|
|
.help = "enable or disable reporting register access errors",
|
|
.usage = "('enable'|'disable')"
|
|
},
|
|
{
|
|
.name = "gdb_breakpoint_override",
|
|
.handler = handle_gdb_breakpoint_override_command,
|
|
.mode = COMMAND_ANY,
|
|
.help = "Display or specify type of breakpoint "
|
|
"to be used by gdb 'break' commands.",
|
|
.usage = "('hard'|'soft'|'disable')"
|
|
},
|
|
{
|
|
.name = "gdb_target_description",
|
|
.handler = handle_gdb_target_description_command,
|
|
.mode = COMMAND_CONFIG,
|
|
.help = "enable or disable target description",
|
|
.usage = "('enable'|'disable')"
|
|
},
|
|
{
|
|
.name = "gdb_save_tdesc",
|
|
.handler = handle_gdb_save_tdesc_command,
|
|
.mode = COMMAND_EXEC,
|
|
.help = "Save the target description file",
|
|
},
|
|
COMMAND_REGISTRATION_DONE
|
|
};
|
|
|
|
int gdb_register_commands(struct command_context *cmd_ctx)
|
|
{
|
|
gdb_port = strdup("3333");
|
|
gdb_port_next = strdup("3333");
|
|
return register_commands(cmd_ctx, NULL, gdb_command_handlers);
|
|
}
|
|
|
|
void gdb_set_frontend_state_running(struct connection *connection)
|
|
{
|
|
struct gdb_connection *gdb_con = connection->priv;
|
|
gdb_con->frontend_state = TARGET_RUNNING;
|
|
}
|
|
|
|
void gdb_service_free(void)
|
|
{
|
|
free(gdb_port);
|
|
free(gdb_port_next);
|
|
}
|