There has been a random bug (that occurs frequently outside the debugger
but rarely inside the debugger, of course) that seemed to be caused by
inconsistent page mappings. Sometimes loading an ELF would work. Other
times loading that same ELF, the loader would complain of missing
sections or invalid headers. Worse, occasionally program execution would
jump off into random memory for no reason I could see by examining the
disassembly. This issue has been plauging me FOR A YEAR and I've been
pulling my hair out trying to find it.
https://stackoverflow.com/a/28384866
Eventually this stack overflow answer to a different question about
INVLPG gave me a hint that the 'accessed' flag of page table entries
might not be set on pages even if they end up in the TLB.
Good riddance to this damned bug.
j6libc.py was initially made for libc to generate stdint.h, but it
gained a few things that aren't libc-specific. Move it to a package and
split the int-types-specific code into codegen.int_types.
With the new SysV style process init args, it's a bit easier to finally
parse out argc, argv, and envp from the stack and pass them on to main
functions.
Forgot to include this in the change for ld.so to run global ctors for
all modules. The main module itself will have its ctors run last, by
crt0, so mark it as not needing ctors in ld.so.
Added a j6_proto_vfs_tag/_get_tag pair of messages to the VFS protocol
to allow filesystems to label themselves, and gave 6s the concept of
current fs and current working directory.
With the move to dynamic executables, crt0's _start was only ever
calling libc's __init_libc, which only ran libc's init_array list. Now
make crt0 itself (which is statically linked into every executable) call
it's own init_array list and have ld.so call every other image's ctor
lists.
I had defined hook-quit in the GDB script to kill the inferior. But if
the inferior had already terminated, then trying to quit GDB only
printed an error. Now hook-quit is smarter and checks if the inferior is
running before trying to kill it.
The mkj6romfs.py script always wrote uncompessed directory info to the
initrd image, even if compressed was smaller - but it would write the
metadata as if it had compressed it.
This commit adds the 6s shell, and a bunch of supporting work for it.
Major changes include:
- New shell.yaml manifest to give 6s control of the TTY instead of
srv.logger
- Changes to mailbox syscalls to add max handles array size separate
from input size. Also reversed the meaning of the similar data size
argument in those syscalls. (Using the second arg as the max array
size and the first as the current valid size allows for the auto
verify code to verify handles properly, and simplifies user-side
code.)
- New util::unique_ptr smart pointer class similar to std::unique_ptr
- New ipc::message format that uses util::unique_ptr to manage ownership
and lifetimes and avoid extra copying.
- The service locator protocol now supports multiple handles per entry
- Channels got a major overhaul. They are now split into two VMAs, each
containing a mutex, a condition, and a util::bip_buffer. The order of
the VMAs determines which end of the pipe you're on. (ie, the creator
swaps them before handing them to the other thread.) Their API also
changed to be similar to that of util::bip_buffer, to avoid extra
copies.
- util::bip_buffer now keeps its state and its buffer together, so that
there are no pointers. This allows multiple processes to share them in
shared memory, like in channels.
- The UART driver changed from keeping buffers for the serial ports to
just keeping a channel, and the serial port objects read/write
directly from/to the channel.
Known issues:
- The shell doesn't actually do anything yet. It echos its input back to
the serial line and injects a prompt on new lines.
- The shell is one character behind in printing back to the serial line.
The UART driver would constantly hang in unpredictable spots. Turns out
it could get into a situation where it was stuck in a loop unable to
read more from the receive channel, and/or write to the serial port
buffer. Now we use a ring buffer to read as much as possible from the
receive channel, write as much as possible to the serial port buffer,
and move on without looping.
The enum_bitfields system never worked quite right, and always had edge cases where name
resolution for the SFINAE would fail. Move everything over to use util::bitset, which can
be constexpr and boils down to inline integer bitops in release mode.
Improved util::bitset itself, moving the array-backed base implementation into a new
util::sized_bitset, and making the single-inttype backed implementation the base case.
Also added a distinction between | or |= (which work with real bit values) and + or +=
(which work with bit indexes).
Added a release config, and fixed a few spots where optimizations broke things:
- Clang was generating incorrect code for run_ctor_list in libc's init.cpp (it
ignored a check for the end of the list)
- my rep movsb memcpy implementation used incorrect inline asm constraints, so
it was returning a pointer to the end of the copied range instead of the start.
Since this function was just inline asm anyway, I rewrote it in asm by hand in
a new memutils.s file.
User code can now set the log area and severity of log messages. This also updates the j6_log
syscall to take these parameters, but removes all calls to it except through j6::syslog().
Added a new IPC log category, and logging of mailbox calls in it. Also added
some logs in init's service locator to help track down the bug fixed in the
previous commit.
Now the init args are a linked list - this also means ld.so can use its
own plus those of the program (eg, SLP and VFS handles). __init_libj6
now adds the head of the list to its global init_args structure, and the
j6_find_init_handle function can be used to find a handle in those args
for a given proto.
This fixes situations like the logger using the wrong mailbox for the
service locator and never finding the uart driver.
Now when loading a process, init will push all the loader args, align
the stack so that the next pointer will have correct alignment, then
push a pointer to the loader args.
Also:
* _ldso_start will pop all loader args off of the stack before jumping
to the loaded program's entrypoint, as if it had never run.
* The sentinel arg structures have a size instead of being all zeros, so
that they can be popped off properly when done.
time.h and wctype.h had "#error not yet implemented" in them. Now time.h is correct (though the functions
are only declared), and wctype.h exists enough to define its types. Also a dlsym stub was added that just
returns 0.
Update init to use the new xoroshiro RNG to create a random load address for dynamic executables.
Also pass an address past the end of the loader in the loader_arg to use when loading dynamic
dependencies.
ld.so will now go through all DT_NEEDED entries in the dynamic table and load and relocate
those shared libraries as well. Lazy linking of functions via the PLT is not yet supported,
all PLT entries are looked up ahead of time by ld.so.
Passing a size of 0 in to vma_resize will now not attempt to alter the VMA size, but will
still put the size into the passed-in pointer. Using this allows querying the size of a
VMA without changing it.
Stacks were not 16-byte aligned when coming into j6::thread::init_proc,
make sure it aligns stacks for SSE by adding the `force_align_arg_pointer`
attribute.