Some information on using Segger JLink to OpenOCD GDB debug an ESP32 project, specifically my WIP wolfSSL SSH Server.

ESP32 JTAG Pinout Wiring; Segger J-Link using WinUSB (v6.1.7600.16385)

TDI -> GPIO12
TCK -> GPIO13
TMS -> GPIO14
TDO -> GPIO15
TRST -> EN / RST (Reset)
GND -> GND

See Espressif JTAG Debugging docs.

Install Espressif Standard Setup of Toolchain for Windows.

Proceed to install WSL toolchain components:

Apparently the xtensa-esp32-elf expects python2.7 with libpython2.7 installed:

sudo apt-get install python
apt-get install libpython2.7

sudo apt-get install make libtool pkg-config autoconf automake texinfo
sudo apt-get install libusb-1.0

In WSL, install the Espressif openOCD-esp32:

cd /mnt/c/workspace/
git clone --recursive https://github.com/espressif/openocd-esp32.git
cd openocd-esp32
./bootstrap
./configure

Successful output should end like this:

configure: creating ./config.status
config.status: creating Makefile
config.status: creating libjaylink/Makefile
config.status: creating libjaylink/version.h
config.status: creating libjaylink.pc
config.status: creating Doxyfile
config.status: creating config.h
config.status: executing depfiles commands
config.status: executing libtool commands

libjaylink configuration summary:
 - Package version ................ 0.2.0-git-f73ad5e
 - Library version ................ 0:0:0
 - Installation prefix ............ /usr/local
 - Building on .................... x86_64-pc-linux-gnu
 - Building for ................... x86_64-pc-linux-gnu

Enabled transports:
 - USB ............................ yes
 - TCP ............................ yes



OpenOCD configuration summary
--------------------------------------------------
MPSSE mode of FTDI based devices        yes (auto)
cJTAG OSCAN1 tunneled thru MPSSE        yes (auto)
ST-Link Programmer                      yes (auto)
TI ICDI JTAG Programmer                 yes (auto)
Keil ULINK JTAG Programmer              yes (auto)
Altera USB-Blaster II Compatible        yes (auto)
Bitbang mode of FT232R based devices    yes (auto)
Versaloon-Link JTAG Programmer          yes (auto)
TI XDS110 Debug Probe                   yes (auto)
OSBDM (JTAG only) Programmer            yes (auto)
eStick/opendous JTAG Programmer         yes (auto)
Espressif JTAG Programmer               yes (auto)
Andes JTAG Programmer                   yes (auto)
USBProg JTAG Programmer                 yes (auto)
Raisonance RLink JTAG Programmer        yes (auto)
Olimex ARM-JTAG-EW Programmer           yes (auto)
CMSIS-DAP Compliant Debugger            no
Cypress KitProg Programmer              no
Altera USB-Blaster Compatible           yes (auto)
ASIX Presto Adapter                     yes (auto)
OpenJTAG Adapter                        yes (auto)
SEGGER J-Link Programmer                yes (auto)

Then make and make install (this may take a long time):

cd /mnt/c/workspace/openocd-esp32
make
sudo make install
openocd --version

The reported openocd version should look something like Open On-Chip Debugger v0.10.0-esp32-20210902 (2021-09-11-13:52).

In a DOS Window (recall WSL doesn’t support native USB for Segger J-Link), run openocd:

cd c:\workspace\openocd-esp32\tcl
openocd -f interface/jlink.cfg -c "adapter_khz 4000" -f target/esp32.cfg

If errors are encountered, try:

  • check JTAG connections
  • try another ESP32 board
  • try another USB cable
  • setting different adapter_khz speeds
  • repeated launch, try again.

See JTAG Debugging Using Debugger for the gdbinit file:

target remote :3333
set remote hardware-watchpoint-limit 2
mon reset halt
flushregs
thb app_main
c

In another Window, in this case WSL, run the xtensa-esp32-elf-gdb:

# in case it is in a different locaion:
# 
# /home/gojimmypi/.espressif/tools/xtensa-esp32-elf/esp-2021r1-8.4.0/xtensa-esp32-elf/bin/

cd  /mnt/c/workspace/wolfssl-esp32/wolfssl_ssh_server/build
xtensa-esp32-elf-gdb wolfssl_ssh_server.elf -x gdbinit --tui

in GDB:

# Next (step over)
n

# 

Config File Settings

For reference, the interface/jlink.cfg looks like this. Be sure to edit with the respective serial number of your device!

#
# SEGGER J-Link
#
# http://www.segger.com/jlink.html
#

interface jlink

# The serial number can be used to select a specific device in case more than
# one is connected to the host.
#
# Example: Select J-Link with serial number 123456789
#
jlink serial 123456789

The target/esp32.cfg looks like this:

# The ESP32 only supports JTAG.
transport select jtag

# Source the ESP common configuration file
source [find target/esp_common.cfg]

# Target specific registers
set EFUSE_MAC_ADDR_REG 0x3ff5A004

if { [info exists CHIPNAME] } {
	set _CHIPNAME $CHIPNAME
} else {
	set _CHIPNAME esp32
}

if { [info exists CPUTAPID] } {
	set _CPUTAPID $CPUTAPID
} else {
	set _CPUTAPID 0x120034e5
}

if { [info exists ESP32_ONLYCPU] } {
	set _ONLYCPU $ESP32_ONLYCPU
} else {
	set _ONLYCPU 3
}

if { [info exists ESP32_FLASH_VOLTAGE] } {
	set _FLASH_VOLTAGE $ESP32_FLASH_VOLTAGE
} else {
	set _FLASH_VOLTAGE 3.3
}

set _CPU0NAME cpu0
set _CPU1NAME cpu1
set _TARGETNAME_0 $_CHIPNAME.$_CPU0NAME
set _TARGETNAME_1 $_CHIPNAME.$_CPU1NAME

jtag newtap $_CHIPNAME $_CPU0NAME -irlen 5 -expected-id $_CPUTAPID
if { $_ONLYCPU != 1 } {
	jtag newtap $_CHIPNAME $_CPU1NAME -irlen 5 -expected-id $_CPUTAPID
} else {
	jtag newtap $_CHIPNAME $_CPU1NAME -irlen 5 -disable -expected-id $_CPUTAPID
}

# PRO-CPU
if { $_RTOS == "none" } {
	target create $_TARGETNAME_0 $_CHIPNAME -endian little -chain-position $_TARGETNAME_0 -coreid 0
} else {
	target create $_TARGETNAME_0 $_CHIPNAME -endian little -chain-position $_TARGETNAME_0 -coreid 0 -rtos $_RTOS
}
configure_esp_workarea $_TARGETNAME_0 0x40090000 0x4000 0x3FFC0000 0x18000
configure_esp_flash_bank $_TARGETNAME_0 $_CHIPNAME $_FLASH_SIZE
# APP-CPU
if { $_ONLYCPU != 1 } {
	if { $_RTOS == "none" } {
		target create $_TARGETNAME_1 $_CHIPNAME -endian little -chain-position $_TARGETNAME_1 -coreid 1
	} else {
		target create $_TARGETNAME_1 $_CHIPNAME -endian little -chain-position $_TARGETNAME_1 -coreid 1 -rtos $_RTOS
	}
	configure_esp_flash_bank $_TARGETNAME_1 $_CHIPNAME $_FLASH_SIZE
	target smp $_TARGETNAME_0 $_TARGETNAME_1
}

$_TARGETNAME_0 esp32 flashbootstrap $_FLASH_VOLTAGE
$_TARGETNAME_0 xtensa maskisr on
$_TARGETNAME_0 xtensa smpbreak BreakIn BreakOut
if { $_SEMIHOST_BASEDIR != "" } {
	$_TARGETNAME_0 esp semihost_basedir $_SEMIHOST_BASEDIR
}
$_TARGETNAME_0 configure -event gdb-attach {
	$_TARGETNAME_0 xtensa smpbreak BreakIn BreakOut
	# necessary to auto-probe flash bank when GDB is connected
	halt
}

if { $_ONLYCPU != 1 } {
	$_TARGETNAME_1 configure -event gdb-attach {
		$_TARGETNAME_1 xtensa smpbreak BreakIn BreakOut
		# necessary to auto-probe flash bank when GDB is connected
		halt
	}
}

if { $_FLASH_SIZE == 0 } {
	gdb_breakpoint_override hard
}

# special function to program ESP32, it differs from the original 'program' that
# it verifies written image by reading flash directly, instead of reading memory mapped flash regions
proc program_esp32 {filename args} {
	echo "WARNING: Function (program_esp32) is deprecated, and may be removed in a future release."
	echo "         Use (program_esp) instead."
	program_esp $filename {*}$args
}

add_help_text program_esp32 "write an image to flash, address is only required for binary images. verify, reset, exit are optional"
add_usage_text program_esp32 "<filename> \[address\] \[verify\] \[reset\] \[exit\]"

The referenced target/esp_common.cfg looks like this:

# Common ESP chips definitions

if { [info exists ESP_RTOS] } {
	set _RTOS "$ESP_RTOS"
} else {
	set _RTOS "FreeRTOS"
}

if { [info exists ESP_SEMIHOST_BASEDIR] } {
	set _SEMIHOST_BASEDIR "$ESP_SEMIHOST_BASEDIR"
} else {
	# by default current dir (when OOCD has been started)
	set _SEMIHOST_BASEDIR "."
}

if { [info exists ESP_FLASH_SIZE] } {
	set _FLASH_SIZE $ESP_FLASH_SIZE
} else {
	set _FLASH_SIZE "auto"
}

proc configure_esp_workarea { TGT CODE_ADDR CODE_SZ DATA_ADDR DATA_SZ } {
	#WARNING: be careful when selecting working ares for code and data, they should not overlap due to ESP32 physical memory mappings
	$TGT configure -work-area-phys $CODE_ADDR -work-area-virt $CODE_ADDR -work-area-size $CODE_SZ -work-area-backup 1
	# since ESP32 cannot use single address space for code and data we need additional working area to keep data
	$TGT configure -alt-work-area-phys $DATA_ADDR -alt-work-area-virt $DATA_ADDR -alt-work-area-size $DATA_SZ -alt-work-area-backup 1
}

proc configure_esp_workarea_backups { wab_list awab_list } {
	set index 0
	foreach tgt [target names] {
		$tgt configure -work-area-backup [lindex $wab_list $index]
		$tgt configure -alt-work-area-backup [lindex $awab_list $index]
		incr $index
	}
}

proc configure_esp_flash_bank { TGT DRV SIZE } {
	set _SIZE SIZE
	if { $SIZE == 0 } {
		echo "WARNING: ESP flash support is disabled!"
		return
	} else {
		if { $SIZE == "auto" } {
			# special value for flash driver
			set _SIZE 0
		}
	}
	# whole flash for programming purposes
	# TODO: remove it when support for GDB's 'load' comand is implemented
	flash bank $TGT.flash $DRV 0x0 $_SIZE 0 0 $TGT
	# So define mapped flash regions as separate flashes
	# OOCD creates memory map using registered flash banks
	flash bank $TGT.irom $DRV 0x0 0 0 0 $TGT
	flash bank $TGT.drom $DRV 0x0 0 0 0 $TGT
}

# special function to program ESP chip, it differs from the original 'program' that
# it verifies written image by reading flash directly, instead of reading memory mapped flash regions
proc program_esp {filename args} {
	set exit 0
	set compress 0
	set restore_clock 0

	set flash_list_size [llength [flash list]]
	if { $flash_list_size == 0} {
		program_error "** ESP flash programming is not supported yet! **" $exit
	}

	foreach arg $args {
		if {[string equal $arg "verify"]} {
			set verify 1
		} elseif {[string equal $arg "reset"]} {
			set reset 1
		} elseif {[string equal $arg "exit"]} {
			set exit 1
		} elseif {[string equal $arg "compress"]} {
			set compress 1
		} elseif {[string equal $arg "restore_clock"]} {
			set restore_clock 1
		} else {
			set address $arg
		}
	}

	# make sure init is called
	if {[catch {init}] != 0} {
		program_error "** OpenOCD init failed **" 1
	}

	# reset target and call any init scripts
	if {[catch {reset init}] != 0} {
		program_error "** Unable to reset target **" $exit
	}

	set wab_list {}
	set awab_list {}
	foreach tgt [target names] {
		lappend wab_list [$tgt cget -work-area-backup]
		$tgt configure -work-area-backup 0
		lappend awab_list [$tgt cget -alt-work-area-backup]
		$tgt configure -alt-work-area-backup 0
	}

	if {$compress == 1} {
		eval esp compression "on"
	} else {
		eval esp compression "off"
	}

	# start programming phase
	echo "** Programming Started **"
	if {[info exists address]} {
		set flash_args "$filename $address"
	} else {
		set flash_args "$filename"
	}

	if {[catch {eval esp flash_stub_clock_boost "on"}] != 0} {
		program_error "** Clock configuration set failed **" $exit
	}

	if {[catch {eval flash write_image erase $flash_args}] == 0} {
		echo "** Programming Finished **"
		if {[info exists verify]} {
			# verify phase
			echo "** Verify Started **"
			if {[catch {eval esp verify_bank_hash 0 $flash_args}] == 0} {
				echo "** Verified OK **"
			} else {
				configure_esp_workarea_backups $wab_list $awab_list
				if {$restore_clock == 1} {
					eval esp flash_stub_clock_boost "off"
				}
				program_error "** Verify Failed **" $exit
			}
		}

		configure_esp_workarea_backups $wab_list $awab_list

		if {$restore_clock == 1} {
			if {[catch {eval esp flash_stub_clock_boost "off"}] != 0} {
				program_error "** Clock configuration restore failed **" $exit
			}
		}

		if {[info exists reset]} {
			# reset target if requested
			echo "** Resetting Target **"
			reset run
		}
	} else {
		configure_esp_workarea_backups $wab_list $awab_list
		if {$restore_clock == 1} {
			eval esp flash_stub_clock_boost "off"
		}
		program_error "** Programming Failed **" $exit
	}

	if {$exit == 1} {
		shutdown
	}
	return
}

add_help_text program_esp "write an image to flash, address is only required for binary images. verify, reset, exit, compress, restore_clock are optional"
add_usage_text program_esp "<filename> \[address\] \[verify\] \[reset\] \[exit\] \[compress\] \[restore_clock\]"

proc program_esp_bins {build_dir filename args} {
	set exit 0
	set compress 0
	set restore_clock 0

	foreach arg $args {
		if {[string equal $arg "reset"]} {
			set reset 1
		} elseif {[string equal $arg "verify"]} {
			set verify 1
		} elseif {[string equal $arg "exit"]} {
			set exit 1
		} elseif {[string equal $arg "compress"]} {
			set compress 1
		} elseif {[string equal $arg "restore_clock"]} {
			set restore_clock 1
		} else {
			echo "** Unsupported arg $arg, skipping **"
		}
	}

	# Open and Read file
	set fp [open [file join $build_dir $filename] r]
	set file_data [read $fp]
	close $fp

	# Decode JSON to dict
	set flasher_args [json::decode $file_data]

	set flash_files [dict get $flasher_args flash_files]

	foreach addr [dict keys $flash_files] {
		set bin_file [dict get $flash_files $addr]
		set bin_file_path [file join $build_dir $bin_file]

		echo "Flashing $bin_file_path at $addr"

		if {[info exists verify]} {
			set flash_args "$bin_file_path $addr verify"
		} else {
			set flash_args "$bin_file_path $addr"
		}

		if {$compress == 1} {
			append flash_args " compress"
		}

		if {$restore_clock == 1} {
			append flash_args " restore_clock"
		}
		
		if {[ catch { eval program_esp  $flash_args} ] == 0} {
			echo "** Flashing done for $bin_file **"
		} else {
			echo "** Flashing Failed **"
			return -1
		}
	}

	# Reset
	if {[info exists reset]} {
		echo "** Resetting Target **"
		reset run
	}

	# Exit
	if {$exit == 1} {
		shutdown
	}

	return 0
}

add_help_text program_esp_bins "write all the images at address specified in flasher_args.json generated while building idf project"
add_usage_text program_esp_bins "<build_dir> flasher_args.json \[verify\] \[reset\] \[exit\] \[compress\] \[restore_clock\]"

proc esp_get_mac {args} {
	global EFUSE_MAC_ADDR_REG
	foreach arg $args {
		if {[string equal $arg "format"]} {
			set format 1
		}
	}

	if { [string equal [target current] esp32c3] } {
		mem2array mac 8 $EFUSE_MAC_ADDR_REG 6
	} else {
		xtensa set_permissive 1
		mem2array mac 8 $EFUSE_MAC_ADDR_REG 6
		xtensa set_permissive 0
	}

	if {[info exists format]}  {
		format %02x:%02x:%02x:%02x:%02x:%02x $mac(5) $mac(4) $mac(3) $mac(2) $mac(1) $mac(0)
	} else {
		format 0x0000%02x%02x%02x%02x%02x%02x $mac(5) $mac(4) $mac(3) $mac(2) $mac(1) $mac(0)
	}
}

add_help_text esp_get_mac "Print MAC address of the chip. Use a `format` argument to return formatted MAC value"
add_usage_text esp_get_mac "\[format\]"

A successful connection to the ESP32 should look something like this:

c:\workspace\openocd-esp32\tcl>openocd -f interface/jlink.cfg -c "adapter_khz 8000" -f target/esp32.cfg
Open On-Chip Debugger  v0.10.0-esp32-20210401 (2021-04-01-15:46)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
adapter speed: 8000 kHz

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : J-Link V10 compiled Aug  9 2021 10:30:48
Info : Hardware version: 10.10
Info : VTarget = 3.290 V
Info : clock speed 8000 kHz
Info : JTAG tap: esp32.cpu0 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
Info : JTAG tap: esp32.cpu1 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
Info : esp32.cpu0: Target halted, PC=0x40145A46, debug_reason=00000001
Info : esp32.cpu1: Target halted, PC=0x4014FD96, debug_reason=00000000
Info : Listening on port 3333 for gdb connections

Visual Micro Debug Attach from Visual Studio

If using the Visual Studio Visual Micro

Manually copy and rename the ESP-IDF build bin and elf files to the Visual Micro temp debug directory for the project name, In this case, a project name GDB test:

C:\Users\gojimmypi\AppData\Local\Temp\VMBuilds\GDB test\esp32_esp32\Debug

Visual Micro OpenmOCD and GDB Notes

Visual Micro has a very old version of ESP32 OpenOCD, and seems to be a hard coded location. Copy a fresh one to:

C:\ProgramData\VMicro\tools\openocd-espressif-esp32-10.0.1\bin

For example, a fresh install was found here for me:

C:\Users\gojimmypi\.espressif\tools\xtensa-esp32-elf\esp-2021r1-8.4.0\xtensa-esp32-elf\bin

When using a Segger JLink, be sure to edit the jlink.cfg files and enable the line with your device serial number.

#
# SEGGER J-Link
#
# http://www.segger.com/jlink.html
#

interface jlink

# The serial number can be used to select a specific device in case more than
# one is connected to the host.
#
# Example: Select J-Link with serial number 123456789
#
jlink serial 123456789

Found in directories such as this one for VisualMicro:

C:\ProgramData\VMicro\tools\openocd-espressif-esp32-10.0.1\share\openocd\scripts\interface

And this one for the ESP-IDF:

C:\Users\gojimmypi\.espressif\tools\openocd-esp32\v0.10.0-esp32-20210401\openocd-esp32\share\openocd\scripts\interface

See also:

OpenOCD

GDB

Espressif

wolfSSL

Visual Micro

Other stuff