mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2026-01-23 02:05:57 +01:00
gdb: create some automated tests with pytest
gem5 baremetal: use m5exit m5op in exit() so as to not force users to apply a patch for almost all examples
This commit is contained in:
32
README.adoc
32
README.adoc
@@ -767,24 +767,22 @@ Every `.c` file inside link:baremetal/[] and `.S` file inside `baremetal/arch/<a
|
|||||||
|
|
||||||
....
|
....
|
||||||
./run --arch arm --baremetal exit
|
./run --arch arm --baremetal exit
|
||||||
./run --arch arm --baremetal arch/arm/semihost_exit
|
./run --arch arm --baremetal arch/arm/add
|
||||||
....
|
....
|
||||||
|
|
||||||
which will run respectively:
|
which will run respectively:
|
||||||
|
|
||||||
* link:baremetal/exit.c[]
|
* link:baremetal/exit.c[]
|
||||||
* link:baremetal/arch/arm/m5exit.S[]
|
* link:baremetal/arch/arm/add.S[]
|
||||||
|
|
||||||
which just make the emulator quit via <<semihosting>>.
|
|
||||||
|
|
||||||
Alternatively, for the sake of tab completion, we also accept relative paths inside `baremetal/`:
|
Alternatively, for the sake of tab completion, we also accept relative paths inside `baremetal/`:
|
||||||
|
|
||||||
....
|
....
|
||||||
./run --arch arm --baremetal baremetal/exit.c
|
./run --arch arm --baremetal baremetal/exit.c
|
||||||
./run --arch arm --baremetal baremetal/arch/arm/semihost_exit.c
|
./run --arch arm --baremetal baremetal/arch/arm/add.S
|
||||||
....
|
....
|
||||||
|
|
||||||
Absolute paths however as used as is an must point to the actual executable:
|
Absolute paths however are used as is and must point to the actual executable:
|
||||||
|
|
||||||
....
|
....
|
||||||
./run --arch arm --baremetal "$(./getvar --arch arm baremetal_build_dir)/exit.elf"
|
./run --arch arm --baremetal "$(./getvar --arch arm baremetal_build_dir)/exit.elf"
|
||||||
@@ -793,7 +791,6 @@ Absolute paths however as used as is an must point to the actual executable:
|
|||||||
To use gem5 instead of QEMU do:
|
To use gem5 instead of QEMU do:
|
||||||
|
|
||||||
....
|
....
|
||||||
patch -d "$(./getvar gem5_src_dir)" -p 1 < patches/manual/gem5-semihost.patch
|
|
||||||
./download-dependencies --baremetal --gem5
|
./download-dependencies --baremetal --gem5
|
||||||
./build-gem5 --arch arm
|
./build-gem5 --arch arm
|
||||||
./build-crosstool-ng --arch arm
|
./build-crosstool-ng --arch arm
|
||||||
@@ -809,8 +806,6 @@ and then <<qemu-buildroot-setup,as usual>> open a shell with:
|
|||||||
|
|
||||||
TODO: the carriage returns are a bit different than in QEMU, see: <<gem5-baremetal-carriage-return>>.
|
TODO: the carriage returns are a bit different than in QEMU, see: <<gem5-baremetal-carriage-return>>.
|
||||||
|
|
||||||
The semihosting patch is required to enable <<semihosting>>, on which base functionality such as `exit()` depends, see also: https://stackoverflow.com/questions/52475268/how-to-enable-arm-semihosting-in-gem5/52475269#52475269
|
|
||||||
|
|
||||||
Note that `./build-baremetal` requires the `--gem5` option, and generates separate executable images for both, as can be seen from:
|
Note that `./build-baremetal` requires the `--gem5` option, and generates separate executable images for both, as can be seen from:
|
||||||
|
|
||||||
....
|
....
|
||||||
@@ -10200,18 +10195,16 @@ and on another shell:
|
|||||||
|
|
||||||
Semihosting is a publicly documented interface specified by ARM Holdings that allows us to do some magic operations very useful in development.
|
Semihosting is a publicly documented interface specified by ARM Holdings that allows us to do some magic operations very useful in development.
|
||||||
|
|
||||||
Semihosting is implemented both on some real devices and on simulators such as QEMU and gem5.
|
Semihosting is implemented both on some real devices and on simulators such as QEMU and <<gem5-semihosting>>.
|
||||||
|
|
||||||
It is documented at: https://developer.arm.com/docs/100863/latest/introduction
|
It is documented at: https://developer.arm.com/docs/100863/latest/introduction
|
||||||
|
|
||||||
Example:
|
For example, the following code makes QEMU exit:
|
||||||
|
|
||||||
....
|
....
|
||||||
./run --arch arm --baremetal arch/arm/semihost_exit
|
./run --arch arm --baremetal arch/arm/semihost_exit
|
||||||
....
|
....
|
||||||
|
|
||||||
makes both the QEMU and gem5 host executables exit.
|
|
||||||
|
|
||||||
Source: link:baremetal/arch/arm/no_bootloader/semihost_exit.S[]
|
Source: link:baremetal/arch/arm/no_bootloader/semihost_exit.S[]
|
||||||
|
|
||||||
That program program contains the code:
|
That program program contains the code:
|
||||||
@@ -10224,11 +10217,10 @@ svc 0x00123456
|
|||||||
|
|
||||||
and we can see from the docs that `0x18` stands for the `SYS_EXIT` command.
|
and we can see from the docs that `0x18` stands for the `SYS_EXIT` command.
|
||||||
|
|
||||||
This is also how we implement the `exit(0)` system call in C for link:baremetal/exit.c[] through the Newlib via the function `_exit` at link:baremetal/lib/common.c[].
|
This is also how we implement the `exit(0)` system call in C for QEMU for link:baremetal/exit.c[] through the Newlib via the function `_exit` at link:baremetal/lib/common.c[].
|
||||||
|
|
||||||
Other magic operations we can do with semihosting besides exiting the on the host include:
|
Other magic operations we can do with semihosting besides exiting the on the host include:
|
||||||
|
|
||||||
* exit
|
|
||||||
* read and write to host stdin and stdout
|
* read and write to host stdin and stdout
|
||||||
* read and write to host files
|
* read and write to host files
|
||||||
|
|
||||||
@@ -10260,6 +10252,16 @@ Bibliography:
|
|||||||
* https://stackoverflow.com/questions/31990487/how-to-cleanly-exit-qemu-after-executing-bare-metal-program-without-user-interve/40957928#40957928
|
* https://stackoverflow.com/questions/31990487/how-to-cleanly-exit-qemu-after-executing-bare-metal-program-without-user-interve/40957928#40957928
|
||||||
* https://balau82.wordpress.com/2010/11/04/qemu-arm-semihosting/
|
* https://balau82.wordpress.com/2010/11/04/qemu-arm-semihosting/
|
||||||
|
|
||||||
|
==== gem5 semihosting
|
||||||
|
|
||||||
|
For gem5, you need:
|
||||||
|
|
||||||
|
....
|
||||||
|
patch -d "$(./getvar gem5_src_dir)" -p 1 < patches/manual/gem5-semihost.patch
|
||||||
|
....
|
||||||
|
|
||||||
|
https://stackoverflow.com/questions/52475268/how-to-enable-arm-semihosting-in-gem5/52475269#52475269
|
||||||
|
|
||||||
=== gem5 baremetal carriage return
|
=== gem5 baremetal carriage return
|
||||||
|
|
||||||
TODO: our example is printing newlines without automatic carriage return `\r` as in:
|
TODO: our example is printing newlines without automatic carriage return `\r` as in:
|
||||||
|
|||||||
13
baremetal/add.c
Normal file
13
baremetal/add.c
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include <common.h>
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
int i, j, k;
|
||||||
|
i = 1;
|
||||||
|
/* test-gdb-op1 */
|
||||||
|
j = 2;
|
||||||
|
/* test-gdb-op2 */
|
||||||
|
k = i + j;
|
||||||
|
/* test-gdb-result */
|
||||||
|
if (k != 3)
|
||||||
|
assert_fail();
|
||||||
|
}
|
||||||
9
baremetal/add.py
Normal file
9
baremetal/add.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
def test(self):
|
||||||
|
self.sendline('tbreak main')
|
||||||
|
self.sendline('continue')
|
||||||
|
self.continue_to('op1')
|
||||||
|
assert self.get_int('i') == 1
|
||||||
|
self.continue_to('op2')
|
||||||
|
assert self.get_int('j') == 2
|
||||||
|
self.continue_to('result')
|
||||||
|
assert self.get_int('k') == 3
|
||||||
12
baremetal/arch/aarch64/add.S
Normal file
12
baremetal/arch/aarch64/add.S
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.global main
|
||||||
|
main:
|
||||||
|
/* 1 + 2 == 3 */
|
||||||
|
mov x0, #1
|
||||||
|
/* test-gdb-op1 */
|
||||||
|
add x1, x0, #2
|
||||||
|
/* test-gdb-result */
|
||||||
|
cmp x1, #3
|
||||||
|
beq 1f
|
||||||
|
bl assert_fail
|
||||||
|
1:
|
||||||
|
ret
|
||||||
7
baremetal/arch/aarch64/add.py
Normal file
7
baremetal/arch/aarch64/add.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
def test(self):
|
||||||
|
self.sendline('tbreak main')
|
||||||
|
self.sendline('continue')
|
||||||
|
self.continue_to('op1')
|
||||||
|
assert self.get_int('$x0') == 1
|
||||||
|
self.continue_to('result')
|
||||||
|
assert self.get_int('$x1') == 3
|
||||||
12
baremetal/arch/arm/add.S
Normal file
12
baremetal/arch/arm/add.S
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.global main
|
||||||
|
main:
|
||||||
|
/* 1 + 2 == 3 */
|
||||||
|
mov r0, #1
|
||||||
|
/* test-gdb-op1 */
|
||||||
|
add r1, r0, #2
|
||||||
|
/* test-gdb-result */
|
||||||
|
cmp r1, #3
|
||||||
|
beq 1f
|
||||||
|
bl assert_fail
|
||||||
|
1:
|
||||||
|
bx lr
|
||||||
7
baremetal/arch/arm/add.py
Normal file
7
baremetal/arch/arm/add.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
def test(self):
|
||||||
|
self.sendline('tbreak main')
|
||||||
|
self.sendline('continue')
|
||||||
|
self.continue_to('op1')
|
||||||
|
assert self.get_int('$r0') == 1
|
||||||
|
self.continue_to('result')
|
||||||
|
assert self.get_int('$r1') == 3
|
||||||
@@ -2,5 +2,4 @@
|
|||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
puts("hello");
|
puts("hello");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ int _write(int file, char *ptr, int len) {
|
|||||||
|
|
||||||
/* Only 0 is supported for now, arm semihosting cannot handle other values. */
|
/* Only 0 is supported for now, arm semihosting cannot handle other values. */
|
||||||
void _exit(int status) {
|
void _exit(int status) {
|
||||||
|
#if defined(GEM5)
|
||||||
|
#if defined(__arm__)
|
||||||
|
__asm__ __volatile__ ("mov r0, #0; mov r1, #0; .inst 0xEE000110 | (0x21 << 16);");
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
__asm__ __volatile__ ("mov x0, #0; .inst 0XFF000110 | (0x21 << 16);");
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
#if defined(__arm__)
|
#if defined(__arm__)
|
||||||
__asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
|
__asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
|
||||||
#elif defined(__aarch64__)
|
#elif defined(__aarch64__)
|
||||||
@@ -76,6 +83,7 @@ void _exit(int status) {
|
|||||||
"hlt 0xf000\n"
|
"hlt 0xf000\n"
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void assert_fail() {
|
void assert_fail() {
|
||||||
|
|||||||
@@ -1,3 +1 @@
|
|||||||
void main(void) {
|
void main(void) {}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|||||||
14
build
14
build
@@ -22,15 +22,15 @@ class Component:
|
|||||||
self.dependencies = []
|
self.dependencies = []
|
||||||
else:
|
else:
|
||||||
self.dependencies = dependencies
|
self.dependencies = dependencies
|
||||||
def build(self, arch, dry_run):
|
def build(self, arch):
|
||||||
if self.build_callback is not None:
|
if self.build_callback is not None:
|
||||||
self.build_callback(arch)
|
self.build_callback(arch)
|
||||||
|
|
||||||
def build_baremetal(arch, dry_run):
|
def build_baremetal(arch):
|
||||||
common.run_cmd(['build-crosstool-ng'], arch)
|
run_cmd(['build-crosstool-ng'], arch)
|
||||||
common.run_cmd(['build-baremetal'], arch)
|
run_cmd(['build-baremetal'], arch)
|
||||||
common.run_cmd(['build-baremetal', '--gem5'], arch)
|
run_cmd(['build-baremetal', '--gem5'], arch)
|
||||||
common.run_cmd(['build-baremetal', '--gem5', '--machine', 'RealViewPBX'], arch)
|
run_cmd(['build-baremetal', '--gem5', '--machine', 'RealViewPBX'], arch)
|
||||||
|
|
||||||
def run_cmd(cmd, arch):
|
def run_cmd(cmd, arch):
|
||||||
global args
|
global args
|
||||||
@@ -220,4 +220,4 @@ for component_name in components:
|
|||||||
# Do the build.
|
# Do the build.
|
||||||
for arch in archs:
|
for arch in archs:
|
||||||
for component in selected_components:
|
for component in selected_components:
|
||||||
component.build(arch, args.dry_run)
|
component.build(arch)
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ class BaremetalComponent(common.Component):
|
|||||||
bootloader_obj = os.path.join(common.baremetal_build_lib_dir, 'bootloader{}'.format(common.obj_ext))
|
bootloader_obj = os.path.join(common.baremetal_build_lib_dir, 'bootloader{}'.format(common.obj_ext))
|
||||||
common_obj = os.path.join(common.baremetal_build_lib_dir, 'common{}'.format(common.obj_ext))
|
common_obj = os.path.join(common.baremetal_build_lib_dir, 'common{}'.format(common.obj_ext))
|
||||||
cflags = [
|
cflags = [
|
||||||
|
'-I', common.baremetal_src_lib_dir, common.Newline,
|
||||||
|
'-O0', common.Newline,
|
||||||
'-ggdb3', common.Newline,
|
'-ggdb3', common.Newline,
|
||||||
'-mcpu={}'.format(common.mcpu), common.Newline,
|
'-mcpu={}'.format(common.mcpu), common.Newline,
|
||||||
'-nostartfiles', common.Newline,
|
'-nostartfiles', common.Newline,
|
||||||
'-O0', common.Newline,
|
|
||||||
'-I', common.baremetal_src_lib_dir, common.Newline,
|
|
||||||
]
|
]
|
||||||
if args.prebuilt:
|
if args.prebuilt:
|
||||||
gcc = 'arm-none-eabi-gcc'
|
gcc = 'arm-none-eabi-gcc'
|
||||||
@@ -31,6 +31,7 @@ class BaremetalComponent(common.Component):
|
|||||||
uart_address = 0x10009000
|
uart_address = 0x10009000
|
||||||
else:
|
else:
|
||||||
raise Exception('unknown machine: ' + common.machine)
|
raise Exception('unknown machine: ' + common.machine)
|
||||||
|
cflags.extend(['-D', 'GEM5'.format(uart_address), common.Newline])
|
||||||
else:
|
else:
|
||||||
entry_address = 0x40000000
|
entry_address = 0x40000000
|
||||||
uart_address = 0x09000000
|
uart_address = 0x09000000
|
||||||
@@ -50,8 +51,7 @@ class BaremetalComponent(common.Component):
|
|||||||
cflags +
|
cflags +
|
||||||
[
|
[
|
||||||
'-c', common.Newline,
|
'-c', common.Newline,
|
||||||
'-D', common.Newline,
|
'-D', 'UART0_ADDR={:#x}'.format(uart_address), common.Newline,
|
||||||
'UART0_ADDR={:#x}'.format(uart_address), common.Newline,
|
|
||||||
'-o', common_obj, common.Newline,
|
'-o', common_obj, common.Newline,
|
||||||
os.path.join(common.baremetal_src_lib_dir, 'common' + common.c_ext), common.Newline,
|
os.path.join(common.baremetal_src_lib_dir, 'common' + common.c_ext), common.Newline,
|
||||||
]
|
]
|
||||||
|
|||||||
31
common.py
31
common.py
@@ -657,7 +657,7 @@ def run_cmd(
|
|||||||
#sigpipe_old = signal.getsignal(signal.SIGPIPE)
|
#sigpipe_old = signal.getsignal(signal.SIGPIPE)
|
||||||
#signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
#signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||||
|
|
||||||
cmd = [x for x in cmd if x != this_module.Newline]
|
cmd = this_module.strip_newlines(cmd)
|
||||||
if not dry_run and not this_module.dry_run:
|
if not dry_run and not this_module.dry_run:
|
||||||
# https://stackoverflow.com/questions/15535240/python-popen-write-to-stdout-and-log-file-simultaneously/52090802#52090802
|
# https://stackoverflow.com/questions/15535240/python-popen-write-to-stdout-and-log-file-simultaneously/52090802#52090802
|
||||||
with subprocess.Popen(cmd, stdout=stdout, stderr=stderr, env=env, **kwargs) as proc:
|
with subprocess.Popen(cmd, stdout=stdout, stderr=stderr, env=env, **kwargs) as proc:
|
||||||
@@ -927,20 +927,13 @@ def setup(parser):
|
|||||||
this_module.baremetal_build_dir,
|
this_module.baremetal_build_dir,
|
||||||
this_module.baremetal_build_ext,
|
this_module.baremetal_build_ext,
|
||||||
)
|
)
|
||||||
|
this_module.source_path = glob.glob(os.path.splitext(os.path.join(
|
||||||
|
this_module.baremetal_src_dir,
|
||||||
|
os.path.relpath(path, this_module.baremetal_build_dir)
|
||||||
|
))[0] + '.*')[0]
|
||||||
this_module.image = path
|
this_module.image = path
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def setup_dry_run_arguments(args):
|
|
||||||
this_module.dry_run = args.dry_run
|
|
||||||
|
|
||||||
def shlex_split(string):
|
|
||||||
'''
|
|
||||||
shlex_split, but also add Newline after every word.
|
|
||||||
|
|
||||||
Not perfect since it does not group arguments, but I don't see a solution.
|
|
||||||
'''
|
|
||||||
return this_module.add_newlines(shlex.split(string))
|
|
||||||
|
|
||||||
def resolve_executable(in_path, magic_in_dir, magic_out_dir, out_ext):
|
def resolve_executable(in_path, magic_in_dir, magic_out_dir, out_ext):
|
||||||
if os.path.isabs(in_path):
|
if os.path.isabs(in_path):
|
||||||
return in_path
|
return in_path
|
||||||
@@ -967,6 +960,20 @@ def resolve_userland(path):
|
|||||||
this_module.userland_build_ext,
|
this_module.userland_build_ext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def setup_dry_run_arguments(args):
|
||||||
|
this_module.dry_run = args.dry_run
|
||||||
|
|
||||||
|
def shlex_split(string):
|
||||||
|
'''
|
||||||
|
shlex_split, but also add Newline after every word.
|
||||||
|
|
||||||
|
Not perfect since it does not group arguments, but I don't see a solution.
|
||||||
|
'''
|
||||||
|
return this_module.add_newlines(shlex.split(string))
|
||||||
|
|
||||||
|
def strip_newlines(cmd):
|
||||||
|
return [x for x in cmd if x != this_module.Newline]
|
||||||
|
|
||||||
def write_configs(config_path, configs, config_fragments=None):
|
def write_configs(config_path, configs, config_fragments=None):
|
||||||
"""
|
"""
|
||||||
Write extra configs into the Buildroot config file.
|
Write extra configs into the Buildroot config file.
|
||||||
|
|||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pexpect==4.6.0
|
||||||
9
run
9
run
@@ -152,12 +152,11 @@ def main(args, extra_args=None):
|
|||||||
cmd.extend(
|
cmd.extend(
|
||||||
[
|
[
|
||||||
common.executable, common.Newline,
|
common.executable, common.Newline,
|
||||||
'--debug-file=trace.txt', common.Newline
|
'--debug-file=trace.txt', common.Newline,
|
||||||
|
'--listener-mode', 'on', common.Newline,
|
||||||
|
'--outdir', common.m5out_dir, common.Newline,
|
||||||
] +
|
] +
|
||||||
gem5_exe_args +
|
gem5_exe_args
|
||||||
[
|
|
||||||
'-d', common.m5out_dir, common.Newline
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
if args.userland is not None:
|
if args.userland is not None:
|
||||||
cmd.extend([
|
cmd.extend([
|
||||||
|
|||||||
104
run-gdb
104
run-gdb
@@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import imp
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
import common
|
import common
|
||||||
|
|
||||||
@@ -14,10 +15,70 @@ defaults = {
|
|||||||
'kgdb': False,
|
'kgdb': False,
|
||||||
'no_continue': False,
|
'no_continue': False,
|
||||||
'no_lxsymbols': False,
|
'no_lxsymbols': False,
|
||||||
|
'test': False,
|
||||||
'sim': False,
|
'sim': False,
|
||||||
'userland': None,
|
'userland': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GdbTestcase:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
source_path,
|
||||||
|
test_script_path,
|
||||||
|
cmd,
|
||||||
|
debug=False
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
:param debug: if True, print extra debug information to help understand
|
||||||
|
why a test is not working
|
||||||
|
'''
|
||||||
|
self.prompt = '\(gdb\) '
|
||||||
|
self.source_path = source_path
|
||||||
|
common.print_cmd(cmd)
|
||||||
|
cmd = common.strip_newlines(cmd)
|
||||||
|
import pexpect
|
||||||
|
self.child = pexpect.spawn(
|
||||||
|
cmd[0],
|
||||||
|
cmd[1:],
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
if debug:
|
||||||
|
self.child.logfile = sys.stdout
|
||||||
|
self.child.setecho(False)
|
||||||
|
self.child.expect(self.prompt)
|
||||||
|
test = imp.load_source('test', test_script_path)
|
||||||
|
test.test(self)
|
||||||
|
self.child.sendcontrol('d')
|
||||||
|
self.child.close()
|
||||||
|
|
||||||
|
def before(self):
|
||||||
|
return self.child.before.rstrip()
|
||||||
|
|
||||||
|
def continue_to(self, lineid):
|
||||||
|
line_number = self.find_line(lineid)
|
||||||
|
self.sendline('tbreak {}'.format(line_number))
|
||||||
|
self.sendline('continue')
|
||||||
|
|
||||||
|
def get_int(self, int_id):
|
||||||
|
self.sendline('printf "%d\\n", {}'.format(int_id))
|
||||||
|
return int(self.before())
|
||||||
|
|
||||||
|
def find_line(self, lineid):
|
||||||
|
'''
|
||||||
|
Search for the first line that contains a comment line
|
||||||
|
that ends in /* test-gdb-<lineid> */ and return the line number.
|
||||||
|
'''
|
||||||
|
lineend = '/* test-gdb-' + lineid + ' */'
|
||||||
|
with open(self.source_path, 'r') as f:
|
||||||
|
for i, line in enumerate(f):
|
||||||
|
if line.rstrip().endswith(lineend):
|
||||||
|
return i + 1
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def sendline(self, line):
|
||||||
|
self.child.sendline(line)
|
||||||
|
self.child.expect(self.prompt)
|
||||||
|
|
||||||
def main(args, extra_args=None):
|
def main(args, extra_args=None):
|
||||||
'''
|
'''
|
||||||
:param args: argparse parse_argument() output. Must contain all the common options,
|
:param args: argparse parse_argument() output. Must contain all the common options,
|
||||||
@@ -34,6 +95,14 @@ def main(args, extra_args=None):
|
|||||||
args = common.resolve_args(defaults, args, extra_args)
|
args = common.resolve_args(defaults, args, extra_args)
|
||||||
after = common.shlex_split(args.after)
|
after = common.shlex_split(args.after)
|
||||||
before = common.shlex_split(args.before)
|
before = common.shlex_split(args.before)
|
||||||
|
no_continue = args.no_continue
|
||||||
|
if args.test:
|
||||||
|
no_continue = True
|
||||||
|
before.extend([
|
||||||
|
'-q', common.Newline,
|
||||||
|
'-nh', common.Newline,
|
||||||
|
'-ex', 'set confirm off', common.Newline
|
||||||
|
])
|
||||||
if args.break_at is not None:
|
if args.break_at is not None:
|
||||||
break_at = ['-ex', 'break {}'.format(args.break_at), common.Newline]
|
break_at = ['-ex', 'break {}'.format(args.break_at), common.Newline]
|
||||||
else:
|
else:
|
||||||
@@ -43,6 +112,7 @@ def main(args, extra_args=None):
|
|||||||
image = common.resolve_userland(args.userland)
|
image = common.resolve_userland(args.userland)
|
||||||
elif args.baremetal:
|
elif args.baremetal:
|
||||||
image = common.image
|
image = common.image
|
||||||
|
test_script_path = os.path.splitext(common.source_path)[0] + '.py'
|
||||||
else:
|
else:
|
||||||
image = common.vmlinux
|
image = common.vmlinux
|
||||||
if args.baremetal:
|
if args.baremetal:
|
||||||
@@ -70,7 +140,7 @@ def main(args, extra_args=None):
|
|||||||
])
|
])
|
||||||
if not args.kgdb:
|
if not args.kgdb:
|
||||||
cmd.extend(break_at)
|
cmd.extend(break_at)
|
||||||
if not args.no_continue:
|
if not no_continue:
|
||||||
# ## lx-symbols
|
# ## lx-symbols
|
||||||
#
|
#
|
||||||
# ### lx-symbols after continue
|
# ### lx-symbols after continue
|
||||||
@@ -94,14 +164,21 @@ def main(args, extra_args=None):
|
|||||||
if not args.no_lxsymbols and linux_full_system:
|
if not args.no_lxsymbols and linux_full_system:
|
||||||
cmd.extend(['-ex', 'lx-symbols {}'.format(common.kernel_modules_build_subdir), common.Newline])
|
cmd.extend(['-ex', 'lx-symbols {}'.format(common.kernel_modules_build_subdir), common.Newline])
|
||||||
cmd.extend(after)
|
cmd.extend(after)
|
||||||
# I would rather have cwd be out_rootfs_overlay_dir,
|
if args.test:
|
||||||
# but then lx-symbols cannot fine the vmlinux and fails with:
|
GdbTestcase(
|
||||||
# vmlinux: No such file or directory.
|
common.source_path,
|
||||||
return common.run_cmd(
|
test_script_path,
|
||||||
cmd,
|
cmd
|
||||||
cmd_file=os.path.join(common.run_dir, 'run-gdb.sh'),
|
)
|
||||||
cwd=common.linux_build_dir
|
else:
|
||||||
)
|
# I would rather have cwd be out_rootfs_overlay_dir,
|
||||||
|
# but then lx-symbols cannot fine the vmlinux and fails with:
|
||||||
|
# vmlinux: No such file or directory.
|
||||||
|
return common.run_cmd(
|
||||||
|
cmd,
|
||||||
|
cmd_file=os.path.join(common.run_dir, 'run-gdb.sh'),
|
||||||
|
cwd=common.linux_build_dir
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = common.get_argparse(argparse_args={'description': 'Connect with GDB to an emulator to debug Linux itself'})
|
parser = common.get_argparse(argparse_args={'description': 'Connect with GDB to an emulator to debug Linux itself'})
|
||||||
@@ -129,6 +206,13 @@ See: https://github.com/cirosantilli/linux-kernel-module-cheat#gdb-builtin-cpu-s
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-X', '--no-lxsymbols', default=defaults['no_lxsymbols'], action='store_true'
|
'-X', '--no-lxsymbols', default=defaults['no_lxsymbols'], action='store_true'
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--test', default=defaults['test'], action='store_true',
|
||||||
|
help='''\
|
||||||
|
Run an expect test case instead of interactive usage. For baremetal and userland,
|
||||||
|
the script is a .py file next to the source code.
|
||||||
|
'''
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--userland', default=defaults['userland'],
|
'--userland', default=defaults['userland'],
|
||||||
)
|
)
|
||||||
|
|||||||
24
test-gdb
Executable file
24
test-gdb
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
# QEMU
|
||||||
|
./run --arch arm --background --baremetal add --wait-gdb &
|
||||||
|
./run-gdb --arch arm --baremetal add --test
|
||||||
|
wait
|
||||||
|
./run --arch arm --background --baremetal arch/arm/add --wait-gdb &
|
||||||
|
./run-gdb --arch arm --baremetal arch/arm/add --test
|
||||||
|
wait
|
||||||
|
./run --arch aarch64 --background --baremetal arch/aarch64/add --wait-gdb &
|
||||||
|
./run-gdb --arch aarch64 --baremetal arch/aarch64/add --test
|
||||||
|
wait
|
||||||
|
|
||||||
|
# gem5
|
||||||
|
./run --arch arm --background --baremetal add --gem5 --wait-gdb &
|
||||||
|
./run-gdb --arch arm --baremetal add --gem5 --test
|
||||||
|
wait
|
||||||
|
./run --arch arm --background --baremetal arch/arm/add --gem5 --wait-gdb &
|
||||||
|
./run-gdb --arch arm --baremetal arch/arm/add --gem5 --test
|
||||||
|
wait
|
||||||
|
./run --arch aarch64 --background --baremetal arch/aarch64/add --gem5 --wait-gdb &
|
||||||
|
./run-gdb --arch aarch64 --baremetal arch/aarch64/add --gem5 --test
|
||||||
|
wait
|
||||||
Reference in New Issue
Block a user