fix run-toolchain, qemu-monitor, trace-boot, trace2line, bisect-linux-boot-gem5. Fixes part of #63

I'm sad no one reported qemu-monitor break, that one is kind of important.

count.out arguments broke it as an init program, since the kernel adds trash
parameters to every init.

Is anyone using this repo, I wonder? Keep pushing, keep pushing.
One day it gets good enough, and the whole world will see.
This commit is contained in:
Ciro Santilli 六四事件 法轮功
2019-05-12 00:00:00 +00:00
parent 3cc1b793cb
commit fe9c31f737
13 changed files with 214 additions and 184 deletions

View File

@@ -1891,13 +1891,13 @@ For executables from the link:userland/[] directory such as link:userland/posix/
* Shell 2: * Shell 2:
+ +
.... ....
./run-gdb-user count main ./run-gdb-user userland/posix/count.c main
.... ....
+ +
Alternatively, we could also pass the full path to the executable: Alternatively, we could also pass the full path to the executable:
+ +
.... ....
./run-gdb-user "$(./getvar userland_build_dir)/sleep_forever.out" main ./run-gdb-user "$(./getvar userland_build_dir)/posix/count.out" main
.... ....
+ +
Path resolution is analogous to <<baremetal-setup-getting-started,that of `./run --baremetal`>>. Path resolution is analogous to <<baremetal-setup-getting-started,that of `./run --baremetal`>>.
@@ -1952,7 +1952,7 @@ Non-init process:
* Shell 2: * Shell 2:
+ +
.... ....
./run-gdb-user linux/myinsmod main ./run-gdb-user userland/linux/myinsmod.c main
.... ....
* Shell 1 after the boot finishes: * Shell 1 after the boot finishes:
+ +
@@ -1982,7 +1982,7 @@ We have also double checked the address with:
.... ....
./run-toolchain --arch arm readelf -- \ ./run-toolchain --arch arm readelf -- \
-s "$(./getvar --arch arm kernel_modules_build_subdir)/fops.ko" | \ -s "$(./getvar --arch arm userland_build_dir)/linux/myinsmod.out" | \
grep main grep main
.... ....
@@ -2520,16 +2520,16 @@ Source: link:rootfs_overlay/lkmc/gdbserver.sh[].
And on host: And on host:
.... ....
./run-gdbserver linux/myinsmod ./run-gdbserver userland/linux/myinsmod.c
.... ....
or alternatively with the full path: or alternatively with the path to the executable itself:
.... ....
./run-gdbserver "$(./getvar userland_build_dir)/linux/myinsmod.out" ./run-gdbserver "$(./getvar userland_build_dir)/linux/myinsmod.out"
.... ....
https://reverseengineering.stackexchange.com/questions/8829/cross-debugging-for-arm-mips-elf-with-qemu-toolchain/16214#16214 Bibliography: https://reverseengineering.stackexchange.com/questions/8829/cross-debugging-for-arm-mips-elf-with-qemu-toolchain/16214#16214
=== gdbserver BusyBox === gdbserver BusyBox
@@ -3832,7 +3832,7 @@ gem5 user mode:
make \ make \
-B \ -B \
-C "$(./getvar --arch arm buildroot_build_build_dir)/dhrystone-2" \ -C "$(./getvar --arch arm buildroot_build_build_dir)/dhrystone-2" \
CC="$(./run-toolchain --arch arm --dry gcc)" \ CC="$(./run-toolchain --arch arm --print-tool gcc)" \
CFLAGS=-static \ CFLAGS=-static \
; ;
time \ time \
@@ -5733,7 +5733,7 @@ vermagic: 4.17.0 SMP mod_unload modversions
Module information is stored in a special `.modinfo` section of the ELF file: Module information is stored in a special `.modinfo` section of the ELF file:
.... ....
./run-toolchain readelf -- -SW "$(./getvar target_dir)/module_info.ko" ./run-toolchain readelf -- -SW "$(./getvar kernel_modules_build_subdir)/module_info.ko"
.... ....
contains: contains:
@@ -5745,7 +5745,7 @@ contains:
and: and:
.... ....
./run-toolchain readelf -- -x .modinfo "$(./getvar buildroot_build_build_dir)/module_info.ko" ./run-toolchain readelf -- -x .modinfo "$(./getvar kernel_modules_build_subdir)/module_info.ko"
.... ....
gives: gives:
@@ -9169,9 +9169,9 @@ http://gedare-csphd.blogspot.co.uk/2013/02/adding-simple-io-device-to-gem5.html
=== QEMU monitor === QEMU monitor
The QEMU monitor is a terminal that allows you to send text commands to the QEMU VM: https://en.wikibooks.org/wiki/QEMU/Monitor The QEMU monitor is a magic terminal that allows you to send text commands to the QEMU VM itself: https://en.wikibooks.org/wiki/QEMU/Monitor
On another terminal, run: While QEMU is running, on another terminal, run:
.... ....
./qemu-monitor ./qemu-monitor
@@ -9193,7 +9193,7 @@ Source: link:qemu-monitor[]
`qemu-monitor` uses the `-monitor` QEMU command line option, which makes the monitor listen from a socket. `qemu-monitor` uses the `-monitor` QEMU command line option, which makes the monitor listen from a socket.
Alternatively, from text mode: Alternatively, we can also enter the QEMU monitor from inside `-nographics` <<qemu-text-mode>> with:
.... ....
Ctrl-A C Ctrl-A C
@@ -9208,7 +9208,7 @@ Ctrl-A C
* http://stackoverflow.com/questions/14165158/how-to-switch-to-qemu-monitor-console-when-running-with-curses * http://stackoverflow.com/questions/14165158/how-to-switch-to-qemu-monitor-console-when-running-with-curses
* https://superuser.com/questions/488263/how-to-switch-to-the-qemu-control-panel-with-nographics * https://superuser.com/questions/488263/how-to-switch-to-the-qemu-control-panel-with-nographics
And in graphic mode from the GUI: When in graphic mode, we can do it from the GUI:
.... ....
Ctrl-Alt ? Ctrl-Alt ?
@@ -9216,6 +9216,20 @@ Ctrl-Alt ?
where `?` is a digit `1`, or `2`, or, `3`, etc. depending on what else is available on the GUI: serial, parallel and frame buffer. where `?` is a digit `1`, or `2`, or, `3`, etc. depending on what else is available on the GUI: serial, parallel and frame buffer.
Finally, we can also access QEMU monitor commands directly from <<gdb>> with the `monitor` command:
....
./run-gdb
....
then inside that shell:
....
monitor info qtree
....
This way you can use both QEMU monitor and GDB commands to inspect the guest from inside a single shell! Pretty awesome.
In general, `./qemu-monitor` is the best option, as it: In general, `./qemu-monitor` is the best option, as it:
* works on both modes * works on both modes
@@ -9439,6 +9453,8 @@ We can further use Binutils' `addr2line` to get the line that corresponds to eac
less "$(./getvar --arch x86_64 run_dir)/trace-lines.txt" less "$(./getvar --arch x86_64 run_dir)/trace-lines.txt"
.... ....
The last commands takes several seconds.
The format is as follows: The format is as follows:
.... ....
@@ -11350,7 +11366,7 @@ Note that dots cannot be used as in `1.5G`, so just use Megs as in `1500M` inste
Unfortunately, TODO we don't have a perfect way to find the right value for `BR2_TARGET_ROOTFS_EXT2_SIZE`. One good heuristic is: Unfortunately, TODO we don't have a perfect way to find the right value for `BR2_TARGET_ROOTFS_EXT2_SIZE`. One good heuristic is:
.... ....
du -hsx "$(./getvar --arch arm target_dir)" du -hsx "$(./getvar --arch arm buildroot_target_dir)"
.... ....
Some promising ways to overcome this problem include: Some promising ways to overcome this problem include:

View File

@@ -1,34 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env bash
set -eu
import os ./build-linux --clean "$@"
import shutil ./build-linux "$@"
import sys set +e
./run --eval 'm5 exit' "$@" || status=$?
import common # https://stackoverflow.com/questions/4713088/how-to-use-git-bisect/22592593#22592593
if [ "$status" -eq 125 ] || [ "$status" -gt 127 ]; then
build_linux = common.import_path_relative_root('build-linux') status=1
run = common.import_path_relative_root('run') fi
exit "$status"
parser = self.get_argparse(
argparse_args={
'description': '''Bisect the Linux kernel on gem5 boots.
More information at: https://github.com/cirosantilli/linux-kernel-module-cheat#bisection
'''},
default_args={
'emulators': ['gem5'],
'linux_build_id': 'bisect',
},
)
args = self.setup(parser)
# We need a clean rebuild because rebuilds at different revisions:
# - may fail
# - may not actually rebuild all files, e.g. on header changes
self.rmrf(kwargs['linux_build_dir'])
build_linux.LinuxComponent().do_build(args)
status = run.main(args, {
'eval': 'm5 exit',
})
if status == 125 or status == 127:
status = 1
sys.exit(status)

View File

@@ -7,7 +7,6 @@ import tarfile
import common import common
from shell_helpers import LF from shell_helpers import LF
class DockerComponent(self.Component): class DockerComponent(self.Component):
def get_argparse_args(self): def get_argparse_args(self):
return { return {

View File

@@ -114,8 +114,7 @@ class _Argument:
class CliFunction: class CliFunction:
''' '''
Represent a function that can be called either from Python code, or A function that can be called either from Python code, or from the command line.
from the command line.
Features: Features:
@@ -135,6 +134,10 @@ class CliFunction:
* that decorator API is insane * that decorator API is insane
* CLI + Python for single functions was wontfixed: https://github.com/pallets/click/issues/40 * CLI + Python for single functions was wontfixed: https://github.com/pallets/click/issues/40
+
Oh, and I commented on that issue pointing to this alternative and they deleted my comment:
https://github.com/pallets/click/issues/40#event-2088718624 Lol. It could have been useful
for other Googlers and as an implementation reference.
''' '''
def __call__(self, **kwargs): def __call__(self, **kwargs):
''' '''

View File

@@ -170,7 +170,14 @@ class LkmcCliFunction(cli_function.CliFunction):
Common functionality shared across our CLI functions: Common functionality shared across our CLI functions:
* command timing * command timing
* some common flags, e.g.: --arch, --dry-run, --quiet, --verbose * a lot some common flags, e.g.: --arch, --dry-run, --quiet, --verbose
* a lot of helpers that depend on self.env
+
self.env contains the command line arguments + a ton of values derived from those.
+
It would be beautiful to do this evaluation in a lazy way, e.g. with functions +
cache decorators:
https://stackoverflow.com/questions/815110/is-there-a-decorator-to-simply-cache-function-return-values
''' '''
def __init__( def __init__(
self, self,

View File

@@ -5,41 +5,44 @@ import sys
import telnetlib import telnetlib
import common import common
from shell_helpers import LF
prompt = b'\n(qemu) ' class Main(common.LkmcCliFunction):
def __init__(self):
parser = self.get_argparse({ super().__init__(
'description': '''\ description='''\
Run a command on the QEMU monitor of a running QEMU instance Run a command on the QEMU monitor of a running QEMU instance
If the stdin is a terminal, open an interact shell. Otherwise, If the stdin is a terminal, open an interact shell. Otherwise,
run commands from stdin and quit. run commands from stdin and quit.
''' ''',
}) )
parser.add_argument( self.add_argument(
'command', 'command',
help='If given, run this command and quit', help='If given, run this command and quit',
nargs='*', nargs='*',
) )
args = self.setup(parser)
def write_and_read(tn, cmd, prompt): def timed_main(self):
tn.write(cmd.encode('utf-8')) def write_and_read(tn, cmd, prompt):
return '\n'.join(tn.read_until(prompt).decode('utf-8').splitlines()[1:])[:-len(prompt)] tn.write(cmd.encode('utf-8'))
return '\n'.join(tn.read_until(prompt).decode('utf-8').splitlines()[1:])[:-len(prompt)]
with telnetlib.Telnet('localhost', kwargs['qemu_monitor_port']) as tn: with telnetlib.Telnet('localhost', self.env['qemu_monitor_port']) as tn:
# Couldn't disable server echo, so just removing the write for now. prompt = b'\n(qemu) '
# https://stackoverflow.com/questions/12421799/how-to-disable-telnet-echo-in-python-telnetlib # Couldn't disable server echo, so just removing the write for now.
# sock = tn.get_socket() # https://stackoverflow.com/questions/12421799/how-to-disable-telnet-echo-in-python-telnetlib
# sock.send(telnetlib.IAC + telnetlib.WILL + telnetlib.ECHO) # sock = tn.get_socket()
if os.isatty(sys.stdin.fileno()): # sock.send(telnetlib.IAC + telnetlib.WILL + telnetlib.ECHO)
if kwargs['command'] == []: if os.isatty(sys.stdin.fileno()):
print(tn.read_until(prompt).decode('utf-8'), end='') if self.env['command'] == []:
tn.interact() print(tn.read_until(prompt).decode('utf-8'), end='')
else: tn.interact()
tn.read_until(prompt) else:
print(write_and_read(tn, ' '.join(kwargs['command']) + '\n', prompt)) tn.read_until(prompt)
else: print(write_and_read(tn, ' '.join(self.env['command']) + '\n', prompt))
tn.read_until(prompt) else:
print(write_and_read(tn, sys.stdin.read() + '\n', prompt)) tn.read_until(prompt)
print(write_and_read(tn, sys.stdin.read() + '\n', prompt))
if __name__ == '__main__':
Main().cli()

6
run
View File

@@ -56,7 +56,7 @@ which is what you usually want.
'-E', '-E',
'--eval', '--eval',
help='''\ help='''\
Replace the normal init with a minimal init that just evals the given string. Replace the normal init with a minimal init that just evals the given sh string.
See: https://github.com/cirosantilli/linux-kernel-module-cheat#replace-init See: https://github.com/cirosantilli/linux-kernel-module-cheat#replace-init
chdir into lkmc_home before running the command: chdir into lkmc_home before running the command:
https://github.com/cirosantilli/linux-kernel-module-cheat#lkmc_home https://github.com/cirosantilli/linux-kernel-module-cheat#lkmc_home
@@ -66,8 +66,8 @@ https://github.com/cirosantilli/linux-kernel-module-cheat#lkmc_home
'-F', '-F',
'--eval-after', '--eval-after',
help='''\ help='''\
Pass a base64 encoded command line parameter that gets evalled at the end of Similar to --eval, but the string gets evaled at the last init script,
the normal init. after the normal init finished.
See: https://github.com/cirosantilli/linux-kernel-module-cheat#init-busybox See: https://github.com/cirosantilli/linux-kernel-module-cheat#init-busybox
''' '''
) )

View File

@@ -1,36 +1,42 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import sys
import common import common
rungdb = common.import_path_relative_root('run-gdb')
parser = self.get_argparse(argparse_args={ class Main(common.LkmcCliFunction):
'description': '''GDB step debug guest userland processes without gdbserver. def __init__(self):
super().__init__(
description='''GDB step debug guest userland processes without gdbserver.
More information at: https://github.com/cirosantilli/linux-kernel-module-cheat#gdb-step-debug-userland-processes More information at: https://github.com/cirosantilli/linux-kernel-module-cheat#gdb-step-debug-userland-processes
''' '''
}) )
parser.add_argument( self.add_argument(
'executable', 'executable',
help='Path to the executable to be debugged relative to the Buildroot build directory.' help='Path to the executable to be debugged relative to the Buildroot build directory.'
) )
parser.add_argument( self.add_argument(
'break_at', 'break_at',
default=None, default=None,
help='Break at this point, e.g. main.', help='Break at this point, e.g. main.',
nargs='?' nargs='?'
) )
args = self.setup(parser)
executable = self.resolve_userland_executable(kwargs['executable']) def timed_main(self):
addr = self.get_elf_entry(os.path.join(kwargs['buildroot_build_build_dir'], executable)) raise Exception("This is known to be broken, but fixing shouldn't be too hard! Keyword: get_argparse. See also: https://github.com/cirosantilli/linux-kernel-module-cheat/issues/63")
extra_args = {} executable = self.env['image']
extra_args['before'] = '-ex \"add-symbol-file {} {}\"'.format(executable, hex(addr)) addr = self.get_elf_entry(os.path.join(self.env['buildroot_build_build_dir'], executable))
# Or else lx-symbols throws for arm: args = {}
# gdb.MemoryError: Cannot access memory at address 0xbf0040cc args['before'] = '-ex \"add-symbol-file {} {}\"'.format(executable, hex(addr))
# TODO understand better. # Or else lx-symbols throws for arm:
# Also, lx-symbols overrides the add-symbol-file commands. # gdb.MemoryError: Cannot access memory at address 0xbf0040cc
extra_args['no_lxsymbols'] = True # TODO understand better.
extra_args['break_at'] = kwargs['break_at'] # Also, lx-symbols overrides the add-symbol-file commands.
sys.exit(rungdb.main(args, extra_args)) args['no_lxsymbols'] = True
args['break_at'] = self.env['break_at']
rungdb = common.import_path_main('run-gdb')
return rungdb(**args)
if __name__ == '__main__':
Main().cli()

View File

@@ -1,13 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import sys
import common import common
from shell_helpers import LF from shell_helpers import LF
parser = self.get_argparse(argparse_args={ class Main(common.LkmcCliFunction):
'description': '''Run a Buildroot ToolChain tool like readelf or objdump. def __init__(self):
super().__init__(
defaults = {
'show_time': False,
},
description='''Run a Buildroot ToolChain tool like readelf or objdump.
For example, to get some information about the arm vmlinux: For example, to get some information about the arm vmlinux:
@@ -20,31 +24,40 @@ Get the list of available tools with:
.... ....
ls "$(./getvar -a arm buildroot_host_bin_dir)" ls "$(./getvar -a arm buildroot_host_bin_dir)"
.... ....
''' ''',
}) )
parser.add_argument( self.add_argument(
'--dry', '--print-tool',
help='Just output the tool path to stdout but actually run it', default=False,
) help='''
parser.add_argument('tool', help='Which tool to run.') Just output print tool path to stdout but don't actually run it.
parser.add_argument( Suitable for programmatic consumption by other shell programs.
'extra_args', ''',
default=[], )
help='Extra arguments for the tool.', self.add_argument('tool', help='Which tool to run.')
metavar='extra-args', self.add_argument(
nargs='*' 'extra_args',
) default=[],
args = self.setup(parser) help='Extra arguments for the tool.',
if kwargs['baremetal'] is None: metavar='extra-args',
image = kwargs['vmlinux'] nargs='*'
else: )
image = kwargs['image']
tool= self.get_toolchain_tool(kwargs['tool']) def timed_main(self):
if kwargs['dry']: if self.env['baremetal'] is None:
print(tool) image = self.env['vmlinux']
else: else:
sys.exit(self.sh.run_cmd( image = self.env['image']
[tool, LF] tool = self.get_toolchain_tool(self.env['tool'])
+ self.sh.add_newlines(kwargs['extra_args']), if self.env['print_tool']:
cmd_file=os.path.join(kwargs['run_dir'], 'run-toolchain.sh'), print(tool)
)) return 0
else:
return self.sh.run_cmd(
[tool, LF]
+ self.sh.add_newlines(self.env['extra_args']),
cmd_file=os.path.join(self.env['run_dir'], 'run-toolchain.sh'),
)
if __name__ == '__main__':
Main().cli()

View File

@@ -189,18 +189,18 @@ class ShellHelpers:
os.unlink(path) os.unlink(path)
def run_cmd( def run_cmd(
self, self,
cmd, cmd,
cmd_file=None, cmd_file=None,
out_file=None, out_file=None,
show_stdout=True, show_stdout=True,
show_cmd=True, show_cmd=True,
extra_env=None, extra_env=None,
extra_paths=None, extra_paths=None,
delete_env=None, delete_env=None,
raise_on_failure=True, raise_on_failure=True,
**kwargs **kwargs
): ):
''' '''
Run a command. Write the command to stdout before running it. Run a command. Write the command to stdout before running it.

View File

@@ -17,14 +17,14 @@ More information at: https://github.com/cirosantilli/linux-kernel-module-cheat#t
run = common.import_path_main('run') run = common.import_path_main('run')
if self.env['emulator'] == 'gem5': if self.env['emulator'] == 'gem5':
args['trace'] = 'Exec,-ExecSymbol,-ExecMicro' args['trace'] = 'Exec,-ExecSymbol,-ExecMicro'
run.main(**args) run(**args)
elif self.env['emulator'] == 'qemu': elif self.env['emulator'] == 'qemu':
run_args = args.copy() run_args = args.copy()
run_args['trace'] = 'exec_tb' run_args['trace'] = 'exec_tb'
run_args['quit_after_boot'] = True run_args['quit_after_boot'] = True
run.main(**run_args) run(**run_args)
qemu_trace2txt = common.import_path_main('qemu-trace2txt') qemu_trace2txt = common.import_path_main('qemu-trace2txt')
qemu_trace2txt.main(**args) qemu_trace2txt(**args)
# Instruction count. # Instruction count.
# We could put this on a separate script, but it just adds more arch boilerplate to a new script. # We could put this on a separate script, but it just adds more arch boilerplate to a new script.
# So let's just leave it here for now since it did not add a significant processing time. # So let's just leave it here for now since it did not add a significant processing time.

View File

@@ -8,27 +8,38 @@ now...
''' '''
import os import os
import re
import subprocess
import sys
import common import common
from shell_helpers import LF from shell_helpers import LF
parser = self.get_argparse(argparse_args={ class Main(common.LkmcCliFunction):
'description': 'Convert an execution trace containing PC values into the Linux kernel linex executed' def __init__(self):
}) super().__init__(
args = self.setup(parser) defaults = {
sys.exit(subprocess.Popen([ 'show_time': False,
os.path.join(kwargs['root_dir'], 'trace2line.sh'), },
'true' if kwargs['emulator'] == 'gem5' else 'false', description='''\
kwargs['trace_txt_file'], Convert an execution trace containing PC values into the Linux kernel lines executed.
self.get_toolchain_tool('addr2line'), ''',
kwargs['vmlinux'], )
kwargs['run_dir'],
]).wait())
# This was the full conversion attempt. def timed_main(self):
self.sh.run_cmd([
os.path.join(self.env['root_dir'], 'trace2line.sh'), LF,
'true' if self.env['emulator'] == 'gem5' else 'false', LF,
self.env['trace_txt_file'], LF,
self.get_toolchain_tool('addr2line'), LF,
self.env['vmlinux'], LF,
self.env['run_dir'], LF,
])
if __name__ == '__main__':
Main().cli()
# This was the old full Python port attempt that was failing:
# import subprocess
# import sys
# if kwargs['emulator'] == 'gem5': # if kwargs['emulator'] == 'gem5':
# def get_pc(line): # def get_pc(line):

View File

@@ -8,14 +8,9 @@
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
int main(int argc, char **argv) { int main(void) {
unsigned long i = 0, max; unsigned long i = 0;
if (argc > 1) { while (1) {
max = strtoul(argv[1], NULL, 10);
} else {
max = ULONG_MAX;
}
while (i < max) {
printf("%lu\n", i); printf("%lu\n", i);
i++; i++;
sleep(1); sleep(1);