[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
A tale of bootstrap ld.so debugging
From: |
Ludovic Courtès |
Subject: |
A tale of bootstrap ld.so debugging |
Date: |
Mon, 07 Nov 2011 23:29:53 +0100 |
User-agent: |
Gnus/5.110018 (No Gnus v0.18) Emacs/24.0.90 (gnu/linux) |
Hello!
While cross-building a GNU/Hurd QEMU image, I stumbled upon a series of
bugs, the last one of which led to a one-liner adding libgcc_s.so to the
image.
Lack of libgcc_s.so prevented the initial /hurd/exec to run. The kernel
debugger showed a backtrace in ld.so hinting at an undefined symbol or
missing shared library error:
$ addr2line -pfa -e
/nix/store/xbrr8dwqf2ngipa08l7qsvb8hy1y6cx3-glibc-20110623-i586-pc-gnu/lib/ld.so.1
0x3c82 0x12da7 0x14e64 0x12191 0x1d97 0x12c6d 0x1764c
0x00003c82: _dl_start at ??:0
0x00012da7: _dl_sysdep_start at ??:0
0x00014e64: _hurd_startup at ??:0
0x00012191: go.10655 at ??:0
0x00001d97: dl_main at ??:0
0x00012c6d: _exit at ??:0
0x0001764c: syscall_task_terminate at ??:0
However, ld.so remained mute, as no messages appeared on the console.
It turns out that ld.so is written to speak to the outside world using
io_write RPCs to a term, but no such thing is available at boot time.
Furthermore, at this point ld.so cannot open the Mach console because it
is not granted a send right to the device master port.
So the trick was to:
1. hack Mach to grant ld.so a send right to the
device master port to all the boot tasks, unconditionally;
2. change ld.so’s _dl_sysdep_start to open the Mach console;
3. change libc’s __libc_writev (this is what _dl_printf uses) to use
raw __device_write_inband calls instead of io_write.
The patches below illustrate that in a very crude way. ;-) Actually I
realized that the Mach patch could probably be avoided by just adding
${device-master-port} on the GRUB command-line for ld.so/exec.
Perhaps ld.so could support an additional --device-master-port argument
for that purpose. But then __libc_writev & co. would also need to be
duplicated to support writing to the Mach console.
Thanks,
Ludo’.
diff --git a/sysdeps/mach/hurd/dl-sysdep.c b/sysdeps/mach/hurd/dl-sysdep.c
index 12c39cd..30b1802 100644
--- a/sysdeps/mach/hurd/dl-sysdep.c
+++ b/sysdeps/mach/hurd/dl-sysdep.c
@@ -44,6 +44,8 @@
#include <dl-machine.h>
#include <dl-procinfo.h>
+#include <device/device.h>
+
extern void __mach_init (void);
extern int _dl_argc;
@@ -116,6 +118,29 @@ static void fmh(void) {
/* XXX loser kludge for vm_map kernel bug */
#endif
+/* Return a port to the Mach console. */
+static mach_port_t
+get_console (void)
+{
+ mach_port_t device_master, console;
+#if 0
+ error_t err = __get_privileged_ports (0, &device_master);
+
+ if (err)
+ return MACH_PORT_NULL;
+#else
+ error_t err = 0;
+ device_master = 2;
+#endif
+
+ err = __device_open (device_master, D_WRITE | D_READ, "console", &console);
+ if (err)
+ return MACH_PORT_NULL;
+
+ return console;
+}
+
+static mach_port_t console = MACH_PORT_NULL;
ElfW(Addr)
_dl_sysdep_start (void **start_argptr,
@@ -256,6 +281,25 @@ unfmh(); /* XXX */
/* Set up so we can do RPCs. */
__mach_init ();
+ /* Open the Mach console so that any message can actually be seen. This is
+ particularly useful at boot time, when started by the bootstrap file
+ system. */
+ console = get_console ();
+ if (console != MACH_PORT_NULL)
+ {
+ int written;
+ __device_write_inband (console, 0, 0, "hello, world!\r\n", 15, &written);
+
+ /* _hurd_intern_fd (console, O_WRONLY, 0); */
+ /* _hurd_intern_fd (console, O_WRONLY, 0); */
+ /* _hurd_intern_fd (console, O_WRONLY, 0); */
+
+ struct iovec out = { "hello stdout!\n", 14 };
+ struct iovec err = { "hello stderr!\n", 14 };
+ __writev (STDOUT_FILENO, &out, 1);
+ __writev (STDERR_FILENO, &err, 1);
+ }
+
/* Initialize frequently used global variable. */
GLRO(dl_pagesize) = __getpagesize ();
@@ -393,11 +437,24 @@ __libc_write (int fd, const void *buf, size_t nbytes)
error_t err;
mach_msg_type_number_t nwrote;
+#if 0
assert (fd < _hurd_init_dtablesize);
err = __io_write (_hurd_init_dtable[fd], buf, nbytes, -1, &nwrote);
if (err)
return __hurd_fail (err);
+#else
+ if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
+ {
+ /* Assume the last byte is \n and convert it to \r\n. */
+ int n;
+ __device_write_inband (console, 0, 0, buf, nbytes - 1, &n);
+ nwrote = n + 2;
+ __device_write_inband (console, 0, 0, "\r\n", 2, &n);
+ }
+ else
+ nwrote = 0;
+#endif
return nwrote;
}
@@ -413,6 +470,25 @@ __writev (int fd, const struct iovec *iov, int niov)
return -1;
}
+ if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
+ {
+ /* Assume the last byte is \n and convert it to \r\n. */
+ int i;
+ ssize_t total = 0;
+
+ for (i = 0; i < niov; i++)
+ {
+ int n;
+ __device_write_inband (console, 0, 0,
+ iov[i].iov_base, iov[i].iov_len - 1,
+ &n);
+ total += n + 2;
+ __device_write_inband (console, 0, 0, "\r\n", 2, &n);
+ }
+
+ return total;
+ }
+
int i;
size_t total = 0;
for (i = 0; i < niov; ++i)
And the Mach patch:
diff --git a/kern/bootstrap.c b/kern/bootstrap.c
index c07b032..538f309 100644
--- a/kern/bootstrap.c
+++ b/kern/bootstrap.c
@@ -766,6 +766,22 @@ static void user_bootstrap()
task_suspend (current_task());
+ {
+ mach_port_t host, master;
+ host = task_insert_send_right(current_task(),
+ ipc_port_make_send(realhost.host_priv_self));
+ printf ("inserted send right to host port %d",
+ (int) host);
+ /* master = task_insert_send_right(current_task(), */
+ /*
ipc_port_make_send(master_device_port)); */
+ master = 2;
+ err = mach_port_insert_right(current_task()->itk_space, 2,
+ ipc_port_make_send(master_device_port),
+ MACH_MSG_TYPE_PORT_SEND);
+ printf ("inserted send right to device master port %d (err = %d)",
+ (int) master, err);
+ }
+
/* Tell the bootstrap thread running boot_script_exec_cmd
that we are done looking at INFO. */
simple_lock (&info->lock);
- A tale of bootstrap ld.so debugging,
Ludovic Courtès <=