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);