feat(kernel): implement single-task userspace with ELF64 loader and syscalls

- Add user-accessible page mapping (32-36 MiB) in boot.S with proper permission bits
- Implement ELF64 loader and process management in new process.c module
- Add syscall infrastructure including int 0x80 handler in process-entry.S
- Create userspace programs (sh, ls, cd) with dedicated linker script
- Integrate filesystem path type checking and directory listing functions
- Reserve memory regions for user processes and update kernel memory layout
- Add console input/output support for userspace applications
- Update kernel main loop to start user process after initialization
- Modify Makefile to build and embed userspace ELF images as kernel objects
- Document new userspace capabilities and limitations in README.md
master
韩天峰 4 weeks ago
parent 515132b6b8
commit 0cfa577db1
  1. 45
      examples/typephp-os/Makefile
  2. 56
      examples/typephp-os/README.md
  3. 10
      examples/typephp-os/ROADMAP.md
  4. 7
      examples/typephp-os/freestanding/abi/typephp_os_abi.h
  5. 11
      examples/typephp-os/freestanding/boot.S
  6. 6
      examples/typephp-os/freestanding/bridge.cc
  7. 26
      examples/typephp-os/freestanding/filesystem_abi.cc
  8. 16
      examples/typephp-os/freestanding/kernel.c
  9. 5
      examples/typephp-os/freestanding/memory.c
  10. 64
      examples/typephp-os/freestanding/process-entry.S
  11. 425
      examples/typephp-os/freestanding/process.c
  12. 6
      examples/typephp-os/project.yml
  13. 8
      examples/typephp-os/src/filesystem.php
  14. 4
      examples/typephp-os/src/hardware.stub.php
  15. 8
      examples/typephp-os/src/kernel.php
  16. 9
      examples/typephp-os/tools/test-qemu.sh
  17. 11
      examples/typephp-os/user/cd.c
  18. 15
      examples/typephp-os/user/linker.ld
  19. 15
      examples/typephp-os/user/ls.c
  20. 92
      examples/typephp-os/user/sh.c
  21. 57
      examples/typephp-os/user/syscall.h

@ -6,9 +6,16 @@ PAYLOAD_BIN := $(BUILD)/kernel64.bin
PAYLOAD_OBJECT := $(OBJECT_DIR)/kernel64-payload.o
KERNEL := $(BUILD)/typephp-os.elf
DISK := $(BUILD)/typephp-os.img
USER_SH_ELF := $(BUILD)/sh.elf
USER_LS_ELF := $(BUILD)/ls.elf
USER_CD_ELF := $(BUILD)/cd.elf
USER_IMAGES := $(OBJECT_DIR)/sh-image.o $(OBJECT_DIR)/ls-image.o $(OBJECT_DIR)/cd-image.o
BOOTFLAGS := -m32 -O2 -ffreestanding -fno-pic -fno-pie \
-fno-stack-protector -Wall -Wextra
USER_CFLAGS := -m64 -O2 -ffreestanding -fno-builtin -fno-pic -fno-pie \
-fno-stack-protector -mno-red-zone -Wall -Wextra -Iuser
USER_LDFLAGS := -m elf_x86_64 -nostdlib -static -T user/linker.ld
.PHONY: all payload objects disk run test clean
@ -19,7 +26,7 @@ payload: objects
# Only the 32-bit bootstrap requires flags incompatible with the 64-bit Nano
# payload. Ordinary .c/.cc/.S files stay in project.yml sources.
objects: $(BOOTSTRAP_OBJECT)
objects: $(BOOTSTRAP_OBJECT) $(USER_IMAGES)
disk: $(DISK)
@ -36,6 +43,42 @@ $(OBJECT_DIR):
$(BOOTSTRAP_OBJECT): freestanding/boot.S | $(OBJECT_DIR)
gcc $(BOOTFLAGS) -c $< -o $@
$(OBJECT_DIR)/sh.o: user/sh.c user/syscall.h | $(OBJECT_DIR)
gcc $(USER_CFLAGS) -c $< -o $@
$(OBJECT_DIR)/ls.o: user/ls.c user/syscall.h | $(OBJECT_DIR)
gcc $(USER_CFLAGS) -c $< -o $@
$(OBJECT_DIR)/cd.o: user/cd.c user/syscall.h | $(OBJECT_DIR)
gcc $(USER_CFLAGS) -c $< -o $@
$(USER_SH_ELF): $(OBJECT_DIR)/sh.o user/linker.ld | $(BUILD)
ld $(USER_LDFLAGS) --defsym USER_BASE=0x2000000 -o $@ $<
$(USER_LS_ELF): $(OBJECT_DIR)/ls.o user/linker.ld | $(BUILD)
ld $(USER_LDFLAGS) --defsym USER_BASE=0x2100000 -o $@ $<
$(USER_CD_ELF): $(OBJECT_DIR)/cd.o user/linker.ld | $(BUILD)
ld $(USER_LDFLAGS) --defsym USER_BASE=0x2200000 -o $@ $<
$(OBJECT_DIR)/sh-image.o: $(USER_SH_ELF) | $(OBJECT_DIR)
objcopy -I binary -O elf64-x86-64 -B i386:x86-64 \
--rename-section .data=.rodata.user_elf,alloc,load,readonly,data,contents \
--redefine-sym _binary_build_sh_elf_start=typephp_user_sh_elf_start \
--redefine-sym _binary_build_sh_elf_end=typephp_user_sh_elf_end $< $@
$(OBJECT_DIR)/ls-image.o: $(USER_LS_ELF) | $(OBJECT_DIR)
objcopy -I binary -O elf64-x86-64 -B i386:x86-64 \
--rename-section .data=.rodata.user_elf,alloc,load,readonly,data,contents \
--redefine-sym _binary_build_ls_elf_start=typephp_user_ls_elf_start \
--redefine-sym _binary_build_ls_elf_end=typephp_user_ls_elf_end $< $@
$(OBJECT_DIR)/cd-image.o: $(USER_CD_ELF) | $(OBJECT_DIR)
objcopy -I binary -O elf64-x86-64 -B i386:x86-64 \
--rename-section .data=.rodata.user_elf,alloc,load,readonly,data,contents \
--redefine-sym _binary_build_cd_elf_start=typephp_user_cd_elf_start \
--redefine-sym _binary_build_cd_elf_end=typephp_user_cd_elf_end $< $@
$(KERNEL): payload
objcopy -O binary $(PAYLOAD_ELF) $(PAYLOAD_BIN)
objcopy -I binary -O elf32-i386 -B i386 \

@ -3,8 +3,11 @@
This is a long-running experimental x86_64 TypePHP operating system. It boots
under QEMU and runs an ordinary TypePHP Nano program without linking hosted
PHP, libc, or libstdc++. TypePHP implements the startup self-check, a custom
Zend class, the prime-number demo, and the resident application loop. Small C
and assembly layers provide the machine bootstrap and current kernel services.
Zend class, the prime-number demo, and filesystem services. Small C and
assembly layers provide the machine bootstrap, current kernel services, and
the temporary freestanding userspace programs.
The first userspace programs (`sh`, `ls`, and `cd`) are deliberately written
in freestanding C until tpc can target this small userspace ABI.
The architectural rules and staged plan are maintained in
[ROADMAP.md](ROADMAP.md).
@ -53,12 +56,14 @@ make
make run
```
The build produces two useful files:
The build produces these useful files:
- `build/kernel64.elf`: the 64-bit TypePHP + ordinary Nano payload produced by
tpc;
- `build/typephp-os.elf`: the final Multiboot kernel accepted by QEMU;
- `build/typephp-os.img`: a persistent 32 MiB FAT16 disk image.
- `build/sh.elf`, `build/ls.elf`, and `build/cd.elf`: independent ELF64 Ring-3
programs built without libc.
Run the automated serial-output smoke test with:
@ -66,12 +71,14 @@ Run the automated serial-output smoke test with:
make test
```
The Makefile compiles only the 32-bit Multiboot bootstrap externally because
its `-m32` ABI cannot participate in the 64-bit payload link. All ordinary
The Makefile compiles the 32-bit Multiboot bootstrap externally because its
`-m32` ABI cannot participate in the 64-bit payload link. It also builds the
three deliberately small freestanding C userspace ELF files. All ordinary
64-bit `.c`, `.cc`, and `.S` files remain in `project.yml` and use tpc's generic
`c-flags`, `cxx-flags`, and `asm-flags`. Generic same-ABI prebuilt objects can
be supplied with `objects`; the architecture-changing bootstrap is instead
combined during the final packaging link.
be supplied with `objects`; this is how read-only copies of the user ELF files
are embedded for the kernel ELF loader. The architecture-changing bootstrap is
instead combined during the final packaging link.
After tpc emits `kernel64.elf`, the Makefile uses `objcopy` to turn the payload
into a raw binary and then an ELF32 data object. GNU ld combines that object
@ -88,7 +95,7 @@ qemu-system-x86_64 -m 128M \
-display none -serial stdio -monitor none -no-reboot -no-shutdown
```
Press `Ctrl+A`, then `X`, to leave headless QEMU.
Press `Ctrl+C` to leave headless QEMU.
## Current capabilities
@ -123,5 +130,34 @@ chunks to a future physical page allocator remains later work.
The first filesystem milestone deliberately supports only the FAT16 root
directory and DOS 8.3 names. Nested path traversal, long filenames,
timestamps, permissions, and a general block-device layer remain future work.
Network sockets, dynamic module loading, `include`/`require`/`eval`, and
external process execution remain unavailable.
Network sockets, dynamic module loading, `include`/`require`/`eval`, and PHP
APIs that execute host commands remain unavailable.
## Single-task userspace
After the TypePHP self-check, the kernel validates and loads `sh.elf`, installs
a 64-bit TSS and an IDT gate, and enters Ring 3 with `iretq`. The user pages at
32-36 MiB are marked user-accessible while kernel pages remain supervisor-only.
An `int 0x80` boundary currently provides synchronous `read`, `write`, `exec`,
`exit`, `getcwd`, `chdir`, and directory-list operations. Standard input and
output are backed by QEMU's COM1 serial console.
The shell synchronously executes the separate `ls.elf` and `cd.elf` images.
Only one user context runs at a time: while a command is active, the kernel
keeps the shell register frame and restores it when the command calls `exit`.
Because this is a deliberately single-task model, the working directory is a
session-global property; a successful `cd` therefore remains visible after
control returns to the shell.
Available commands are:
```text
ls
cd DOCS
ls
cd ..
```
The current FAT16 limitation still applies: a root child such as `/DOCS` is a
valid working directory, but nested directory storage is not implemented, so
its listing currently contains only `.` and `..`.

@ -41,11 +41,15 @@ behavior.
directory/stat operations, and PHP's local file stream API. The current
implementation is intentionally limited to root-level DOS 8.3 names;
nested directories and long filenames are next.
6. **Native Class memory.** Exercise Wren GC through Zend MM and verify tracing
6. **Single-task userspace — first slice complete.** ELF64 validation/loading,
supervisor/user page separation, GDT/TSS, Ring-3 entry, synchronous
`int 0x80` system calls, COM1 standard I/O, saved parent context, and
independent freestanding C `sh`, `ls`, and `cd` programs.
7. **Native Class memory.** Exercise Wren GC through Zend MM and verify tracing
of PHPX fields under sustained allocation.
7. **Kernel services.** Interrupt-driven timer, keyboard, physical-page
8. **Kernel services.** Interrupt-driven timer, keyboard, physical-page
reclamation, and a capability-oriented native API.
8. **Packaging and CI.** Automate the two-stage ELF32/ELF64 build and QEMU boot
9. **Packaging and CI.** Automate the two-stage ELF32/ELF64 build and QEMU boot
smoke test in GitHub Actions.
Later architectures may provide different bootstraps and host ABI adapters.

@ -31,6 +31,13 @@ void typephp_os_panic(const char *message);
/* Generic PHPX no-exception policy hook. */
void phpx_no_exception_abort(const char *message);
/* Minimal process/console/filesystem contracts used by the Ring-3 syscall
* boundary. The filesystem implementations continue to live in TypePHP. */
void typephp_os_process_start(void);
long typephp_os_fs_path_type(const char *path);
long typephp_os_fs_list(const char *path, char *buffer, size_t capacity);
long typephp_os_console_read(void *buffer, size_t size);
#ifdef __cplusplus
}
#endif

@ -36,11 +36,13 @@ _start:
# Identity-map the first GiB with 2 MiB pages.
mov $pdp_table, %eax
or $0x3, %eax
# Upper paging levels must permit user traversal. Individual PDEs still
# decide whether a 2 MiB page is supervisor-only or visible to Ring 3.
or $0x7, %eax
mov %eax, pml4_table
mov $pd_table, %eax
or $0x3, %eax
or $0x7, %eax
mov %eax, pdp_table
mov $pd_table, %edi
@ -52,6 +54,11 @@ map_page:
add $8, %edi
loop map_page
# Reserve 32-36 MiB for the single user process image and stack. The
# remaining identity-mapped pages stay supervisor-only.
orl $0x4, pd_table + (16 * 8)
orl $0x4, pd_table + (17 * 8)
# Enable PAE and prepare the x87/SSE state before entering long mode.
mov %cr0, %ecx
and $0xfffffffb, %ecx

@ -19,6 +19,7 @@ extern "C" int typephp_os_disk_available();
extern "C" int typephp_os_disk_read_sector(uint32_t lba, unsigned char *data);
extern "C" int typephp_os_disk_write_sector(uint32_t lba, const unsigned char *data);
extern "C" int typephp_os_disk_flush();
extern "C" void typephp_os_process_start();
extern "C" ZEND_NORETURN void phpx_no_exception_abort(const char *fallback)
{
@ -138,3 +139,8 @@ php::Bool php_kernel_disk_flush()
{
return typephp_os_disk_flush() != 0;
}
void php_kernel_process_start()
{
typephp_os_process_start();
}

@ -155,6 +155,30 @@ php::Bool php_kernel_fs_install(php::Object filesystem)
return installed_filesystem.isObject();
}
extern "C" long typephp_os_fs_path_type(const char *path)
{
if (path == nullptr) {
return -EFAULT;
}
return static_cast<long>(fs_type(php::Str(path)));
}
extern "C" long typephp_os_fs_list(const char *path, char *buffer, size_t capacity)
{
if (path == nullptr || (buffer == nullptr && capacity != 0)) {
return -EFAULT;
}
if (fs_type(php::Str(path)) != 2) {
return -ENOTDIR;
}
php::Str entries = fs_entries(php::Str(path));
const size_t size = entries.length() < capacity ? entries.length() : capacity;
if (size != 0) {
std::memcpy(buffer, entries.data(), size);
}
return static_cast<long>(size);
}
extern "C" int open(const char *path, int flags, ...)
{
if (path == nullptr) {
@ -179,7 +203,7 @@ extern "C" ssize_t read(int fd, void *buffer, size_t count)
return -1;
}
if (fd == STDIN_FILENO) {
return 0;
return static_cast<ssize_t>(typephp_os_console_read(buffer, count));
}
const php::Int size = fs_fd_size(fd);
if (size < 0) {

@ -44,6 +44,22 @@ static void serial_put(uint8_t value)
outb(COM1, value);
}
long typephp_os_console_read(void *buffer, unsigned long size)
{
uint8_t *output = (uint8_t *) buffer;
if (output == 0 || size == 0) {
return 0;
}
/* COM1 is the standard input device for the headless QEMU target. Keep
* this synchronous: the process model intentionally has one foreground
* task and no scheduler yet. */
while ((inb(COM1 + 5) & 0x01u) == 0) {
__asm__ volatile("pause");
}
output[0] = inb(COM1);
return 1;
}
static void vga_scroll(void)
{
if (row < VGA_HEIGHT) {

@ -9,7 +9,10 @@ enum {
static const uint64_t CHUNK_SIZE = 2ul * 1024ul * 1024ul;
/* Keep the complete TypePHP + PHP Nano payload away from the page allocator. */
static const uint64_t KERNEL_RESERVED_END = 16ul * 1024ul * 1024ul;
/* Keep both the kernel payload and the fixed 32-36 MiB Ring-3 area away from
* the Zend arena. This is deliberately simple until a page-frame allocator
* owns user address-space placement. */
static const uint64_t KERNEL_RESERVED_END = 40ul * 1024ul * 1024ul;
static const uint64_t IDENTITY_MAP_END = 1024ul * 1024ul * 1024ul;
typedef struct __attribute__((packed)) {

@ -0,0 +1,64 @@
.section .text
.code64
.global typephp_os_syscall_entry
.type typephp_os_syscall_entry, @function
.extern typephp_os_syscall_dispatch
typephp_os_syscall_entry:
# Save every general-purpose register. The saved RAX slot is overwritten
# with the syscall result before the frame is restored.
push %rbx
push %rcx
push %rdx
push %rsi
push %rdi
push %rbp
push %r8
push %r9
push %r10
push %r11
push %r12
push %r13
push %r14
push %r15
push %rax
mov %rsp, %rdi
call typephp_os_syscall_dispatch
mov %rax, (%rsp)
pop %rax
pop %r15
pop %r14
pop %r13
pop %r12
pop %r11
pop %r10
pop %r9
pop %r8
pop %rbp
pop %rdi
pop %rsi
pop %rdx
pop %rcx
pop %rbx
iretq
.size typephp_os_syscall_entry, . - typephp_os_syscall_entry
.global typephp_os_enter_user
.type typephp_os_enter_user, @function
typephp_os_enter_user:
# RDI = ELF entry, RSI = top of the user stack. Interrupts remain masked;
# int 0x80 is synchronous and the first process manager has no scheduler.
mov $0x1b, %ax
mov %ax, %ds
mov %ax, %es
pushq $0x1b
push %rsi
pushq $0x2
pushq $0x23
push %rdi
iretq
.size typephp_os_enter_user, . - typephp_os_enter_user
.section .note.GNU-stack, "", @progbits

@ -0,0 +1,425 @@
/*
+----------------------------------------------------------------------+
| TypePHP OS |
+----------------------------------------------------------------------+
| Single foreground process, ELF64 loader and int 0x80 syscall core. |
| SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
*/
#include "typephp_os_abi.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
enum {
USER_BEGIN = 32u * 1024u * 1024u,
USER_END = 36u * 1024u * 1024u,
USER_STACK = USER_END - 16u,
USER_CODE_SELECTOR = 0x23,
USER_DATA_SELECTOR = 0x1b,
KERNEL_CODE_SELECTOR = 0x08,
TSS_SELECTOR = 0x28,
IDT_SYSCALL = 0x80,
SYS_READ = 0,
SYS_WRITE = 1,
SYS_EXEC = 59,
SYS_EXIT = 60,
SYS_GETCWD = 79,
SYS_CHDIR = 80,
SYS_READDIR = 217,
};
enum {
ELF_PT_LOAD = 1,
ELF_ET_EXEC = 2,
ELF_MACHINE_X86_64 = 62,
};
typedef struct __attribute__((packed)) {
unsigned char ident[16];
uint16_t type;
uint16_t machine;
uint32_t version;
uint64_t entry;
uint64_t phoff;
uint64_t shoff;
uint32_t flags;
uint16_t ehsize;
uint16_t phentsize;
uint16_t phnum;
uint16_t shentsize;
uint16_t shnum;
uint16_t shstrndx;
} elf64_header;
typedef struct __attribute__((packed)) {
uint32_t type;
uint32_t flags;
uint64_t offset;
uint64_t vaddr;
uint64_t paddr;
uint64_t filesz;
uint64_t memsz;
uint64_t align;
} elf64_program_header;
typedef struct __attribute__((packed)) {
uint16_t limit;
uint64_t base;
} descriptor_pointer;
typedef struct __attribute__((packed)) {
uint16_t offset_low;
uint16_t selector;
uint8_t ist;
uint8_t attributes;
uint16_t offset_middle;
uint32_t offset_high;
uint32_t reserved;
} idt_gate;
typedef struct __attribute__((packed)) {
uint32_t reserved0;
uint64_t rsp0;
uint64_t rsp1;
uint64_t rsp2;
uint64_t reserved1;
uint64_t ist[7];
uint64_t reserved2;
uint16_t reserved3;
uint16_t iomap_base;
} task_state_segment;
typedef struct {
uint64_t rax;
uint64_t r15;
uint64_t r14;
uint64_t r13;
uint64_t r12;
uint64_t r11;
uint64_t r10;
uint64_t r9;
uint64_t r8;
uint64_t rbp;
uint64_t rdi;
uint64_t rsi;
uint64_t rdx;
uint64_t rcx;
uint64_t rbx;
uint64_t rip;
uint64_t cs;
uint64_t rflags;
uint64_t rsp;
uint64_t ss;
} syscall_frame;
typedef struct {
uint64_t pid;
int running;
char cwd[16];
syscall_frame parent_frame;
int parent_waiting;
} process_state;
extern const unsigned char typephp_user_sh_elf_start[];
extern const unsigned char typephp_user_sh_elf_end[];
extern const unsigned char typephp_user_ls_elf_start[];
extern const unsigned char typephp_user_ls_elf_end[];
extern const unsigned char typephp_user_cd_elf_start[];
extern const unsigned char typephp_user_cd_elf_end[];
extern void typephp_os_syscall_entry(void);
extern void typephp_os_enter_user(uint64_t entry, uint64_t stack);
static uint64_t gdt[7] __attribute__((aligned(16)));
static idt_gate idt[256] __attribute__((aligned(16)));
static task_state_segment tss;
static unsigned char syscall_stack[64u * 1024u] __attribute__((aligned(16)));
static process_state foreground_process = {.pid = 1, .cwd = "/"};
static void panic(const char *message)
{
typephp_os_write("Process panic: ", sizeof("Process panic: ") - 1);
typephp_os_panic(message);
}
static int range_inside(uint64_t address, uint64_t size, uint64_t begin, uint64_t end)
{
return address >= begin && size <= end - begin && address <= end - size;
}
static int user_buffer(const void *pointer, size_t size)
{
return range_inside((uint64_t) (uintptr_t) pointer, size, USER_BEGIN, USER_END);
}
static size_t bounded_user_string(const char *string, size_t maximum)
{
size_t length = 0;
if (!user_buffer(string, 1)) {
return (size_t) -1;
}
while (length < maximum && user_buffer(string + length, 1) && string[length] != '\0') {
++length;
}
return length < maximum && user_buffer(string + length, 1) ? length : (size_t) -1;
}
static void install_tss_descriptor(uint64_t base, uint32_t limit)
{
gdt[5] = ((uint64_t) (limit & 0xffffu))
| ((base & 0xffffffu) << 16u)
| (UINT64_C(0x89) << 40u)
| ((uint64_t) ((limit >> 16u) & 0x0fu) << 48u)
| (((base >> 24u) & 0xffu) << 56u);
gdt[6] = base >> 32u;
}
static void install_descriptor_tables(void)
{
descriptor_pointer pointer;
uintptr_t handler = (uintptr_t) typephp_os_syscall_entry;
memset(gdt, 0, sizeof(gdt));
gdt[1] = UINT64_C(0x00af9a000000ffff);
gdt[2] = UINT64_C(0x00cf92000000ffff);
gdt[3] = UINT64_C(0x00cff2000000ffff);
gdt[4] = UINT64_C(0x00affa000000ffff);
memset(&tss, 0, sizeof(tss));
tss.rsp0 = (uint64_t) (uintptr_t) (syscall_stack + sizeof(syscall_stack));
tss.iomap_base = sizeof(tss);
install_tss_descriptor((uint64_t) (uintptr_t) &tss, sizeof(tss) - 1u);
pointer.limit = sizeof(gdt) - 1u;
pointer.base = (uint64_t) (uintptr_t) gdt;
__asm__ volatile("lgdt %0" : : "m"(pointer) : "memory");
__asm__ volatile(
"mov %[data], %%ax\n"
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"mov %%ax, %%ss\n"
"ltr %[tss]\n"
:
: [data] "i"(0x10), [tss] "r"((uint16_t) TSS_SELECTOR)
: "rax", "memory");
memset(idt, 0, sizeof(idt));
idt[IDT_SYSCALL].offset_low = handler & 0xffffu;
idt[IDT_SYSCALL].selector = KERNEL_CODE_SELECTOR;
idt[IDT_SYSCALL].attributes = 0xee; /* present, DPL 3, interrupt gate */
idt[IDT_SYSCALL].offset_middle = (handler >> 16u) & 0xffffu;
idt[IDT_SYSCALL].offset_high = handler >> 32u;
pointer.limit = sizeof(idt) - 1u;
pointer.base = (uint64_t) (uintptr_t) idt;
__asm__ volatile("lidt %0" : : "m"(pointer) : "memory");
}
static uint64_t load_user_elf(const unsigned char *image, const unsigned char *image_end)
{
const size_t image_size = (size_t) (image_end - image);
const elf64_header *header;
if (image_size < sizeof(elf64_header)) {
panic("truncated user ELF\n");
}
header = (const elf64_header *) image;
if (header->ident[0] != 0x7f || header->ident[1] != 'E'
|| header->ident[2] != 'L' || header->ident[3] != 'F'
|| header->ident[4] != 2 || header->ident[5] != 1
|| header->type != ELF_ET_EXEC || header->machine != ELF_MACHINE_X86_64
|| header->phentsize != sizeof(elf64_program_header)) {
panic("invalid user ELF64 header\n");
}
if (!range_inside(header->phoff,
(uint64_t) header->phnum * sizeof(elf64_program_header), 0, image_size)) {
panic("invalid user ELF program table\n");
}
for (uint16_t index = 0; index < header->phnum; ++index) {
const elf64_program_header *segment = (const elf64_program_header *)
(image + header->phoff + (uint64_t) index * sizeof(*segment));
if (segment->type != ELF_PT_LOAD) {
continue;
}
if (segment->filesz > segment->memsz
|| !range_inside(segment->offset, segment->filesz, 0, image_size)
|| !range_inside(segment->vaddr, segment->memsz, USER_BEGIN, USER_END)) {
panic("invalid user ELF load segment\n");
}
memcpy((void *) (uintptr_t) segment->vaddr, image + segment->offset, segment->filesz);
memset((void *) (uintptr_t) (segment->vaddr + segment->filesz),
0, segment->memsz - segment->filesz);
}
if (!range_inside(header->entry, 1, USER_BEGIN, USER_END)) {
panic("invalid user ELF entry\n");
}
return header->entry;
}
static int command_image(
const char *name,
const unsigned char **image,
const unsigned char **image_end)
{
if (strcmp(name, "ls") == 0) {
*image = typephp_user_ls_elf_start;
*image_end = typephp_user_ls_elf_end;
return 1;
}
if (strcmp(name, "cd") == 0) {
*image = typephp_user_cd_elf_start;
*image_end = typephp_user_cd_elf_end;
return 1;
}
return 0;
}
static long syscall_getcwd(char *buffer, size_t size)
{
size_t length = strlen(foreground_process.cwd) + 1u;
if (size < length || !user_buffer(buffer, size)) {
return -1;
}
memcpy(buffer, foreground_process.cwd, length);
return (long) length;
}
static long syscall_chdir(const char *path)
{
char normalized[16];
size_t length = bounded_user_string(path, sizeof(normalized));
if (length == (size_t) -1 || length == 0) {
return -1;
}
if ((length == 1 && path[0] == '/')
|| (length == 2 && path[0] == '.' && path[1] == '.')) {
normalized[0] = '/';
normalized[1] = '\0';
} else if (length == 1 && path[0] == '.') {
return 0;
} else {
size_t source = path[0] == '/' ? 1u : 0u;
size_t name_length = length - source;
if (name_length == 0 || name_length > 12u) {
return -1;
}
normalized[0] = '/';
memcpy(normalized + 1, path + source, name_length);
normalized[name_length + 1u] = '\0';
}
if (typephp_os_fs_path_type(normalized) != 2) {
return -1;
}
memcpy(foreground_process.cwd, normalized, strlen(normalized) + 1u);
return 0;
}
static long syscall_readdir(const char *path, char *buffer, size_t capacity)
{
char resolved[16];
const char *directory = foreground_process.cwd;
if (path != 0) {
size_t length = bounded_user_string(path, sizeof(resolved));
if (length == (size_t) -1) {
return -1;
}
if (length != 0) {
memcpy(resolved, path, length + 1u);
directory = resolved;
}
}
if (!user_buffer(buffer, capacity)) {
return -1;
}
return typephp_os_fs_list(directory, buffer, capacity);
}
static long syscall_exec(syscall_frame *frame, const char *name, const char *argument)
{
const unsigned char *image;
const unsigned char *image_end;
char command[8];
size_t name_length;
if (foreground_process.parent_waiting) {
return -1;
}
name_length = bounded_user_string(name, sizeof(command));
if (name_length == (size_t) -1 || name_length == 0) {
return -1;
}
memcpy(command, name, name_length + 1u);
if (!command_image(command, &image, &image_end)) {
return -1;
}
if (argument != 0 && bounded_user_string(argument, 64) == (size_t) -1) {
return -1;
}
memcpy(&foreground_process.parent_frame, frame, sizeof(*frame));
foreground_process.parent_waiting = 1;
foreground_process.pid = 2;
frame->rip = load_user_elf(image, image_end);
frame->rsp = 35u * 1024u * 1024u - 16u;
frame->rdi = (uint64_t) (uintptr_t) argument;
frame->rsi = 0;
frame->rdx = 0;
return 0;
}
static long syscall_exit(syscall_frame *frame, long status)
{
if (!foreground_process.parent_waiting) {
foreground_process.running = 0;
typephp_os_panic("shell process exited\n");
}
memcpy(frame, &foreground_process.parent_frame, sizeof(*frame));
foreground_process.parent_waiting = 0;
foreground_process.pid = 1;
return status;
}
long typephp_os_syscall_dispatch(syscall_frame *frame)
{
switch (frame->rax) {
case SYS_READ:
if (frame->rdi != 0 || !user_buffer((void *) frame->rsi, frame->rdx)) {
return -1;
}
return typephp_os_console_read((void *) frame->rsi, frame->rdx);
case SYS_WRITE:
if ((frame->rdi != 1 && frame->rdi != 2)
|| !user_buffer((void *) frame->rsi, frame->rdx)) {
return -1;
}
typephp_os_write((const char *) frame->rsi, frame->rdx);
return (long) frame->rdx;
case SYS_EXEC:
return syscall_exec(frame, (const char *) frame->rdi, (const char *) frame->rsi);
case SYS_GETCWD:
return syscall_getcwd((char *) frame->rdi, frame->rsi);
case SYS_CHDIR:
return syscall_chdir((const char *) frame->rdi);
case SYS_READDIR:
return syscall_readdir((const char *) frame->rdi,
(char *) frame->rsi, frame->rdx);
case SYS_EXIT:
return syscall_exit(frame, (long) frame->rdi);
default:
return -1;
}
}
void typephp_os_process_start(void)
{
uint64_t entry;
memset((void *) (uintptr_t) USER_BEGIN, 0, USER_END - USER_BEGIN);
entry = load_user_elf(typephp_user_sh_elf_start, typephp_user_sh_elf_end);
install_descriptor_tables();
foreground_process.running = 1;
typephp_os_write("Process 1: sh.elf (Ring 3)\n",
sizeof("Process 1: sh.elf (Ring 3)\n") - 1);
typephp_os_enter_user(entry, USER_STACK);
panic("Ring-3 entry returned\n");
}

@ -3,8 +3,10 @@ mode: bin
sources:
- src
- freestanding/kernel64-entry.S
- freestanding/process-entry.S
- freestanding/kernel.c
- freestanding/memory.c
- freestanding/process.c
- freestanding/time.c
- freestanding/ata.c
- freestanding/zend-allocator.c
@ -14,6 +16,10 @@ sources:
- freestanding/abi/cxx.cpp
- freestanding/abi/unimplemented.S
- thirdparty/openlibm/src
objects:
- build/objects/sh-image.o
- build/objects/ls-image.o
- build/objects/cd-image.o
build-dir: build/generated
output: build/kernel64.elf
cxx-std: c++17

@ -287,9 +287,15 @@ final class KernelFileSystem
public function entries(string $path): string
{
if (!$this->initialize() || ($path !== '/' && $path !== '' && $path !== '.')) {
if (!$this->initialize() || $this->pathType($path) !== 2) {
return '';
}
if ($path !== '/' && $path !== '' && $path !== '.') {
/* The first FAT16 slice has root directory entries but no nested
* traversal yet. A root child directory is nevertheless a valid
* working directory and contains its logical dot entries. */
return ".\n..\n";
}
$volume = $this->volume->toObject(Fat16Volume::class);
return ".\n..\n" . $volume->rootEntryNames();
}

@ -32,3 +32,7 @@ function kernel_disk_write_sector(int $lba, string $data): bool { return false;
#[NativeFunction]
function kernel_disk_flush(): bool { return false; }
/** Load the embedded ELF shell and transfer control to its Ring-3 entry. */
#[NativeFunction]
function kernel_process_start(): void {}

@ -176,11 +176,7 @@ function main(): void
runMathSelfCheck();
runFilesystemSelfCheck();
runPrimeDemo(100);
$greeting = new KernelGreeting();
while (true) {
sleep(2);
kernel_write($greeting->render(date('Y-m-d H:i:s')), 15);
kernel_put_char(10, 15);
}
echo $greeting->render(date('Y-m-d H:i:s')), "\n";
kernel_process_start();
}

@ -15,7 +15,8 @@ if timeout 8 qemu-system-x86_64 \
-no-reboot \
-no-shutdown \
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
>"${log}" 2>&1; then
>"${log}" 2>&1 \
<<< $'\nls\ncd DOCS\nls\ncd ..\nls\n'; then
status=0
else
status=$?
@ -42,4 +43,10 @@ grep -q "Calculate primes: 0-100" "${log}"
grep -q "Prime count: 25" "${log}"
grep -q "Prime list: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97" "${log}"
grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} Hello TypePHP-OS![[:space:]]*$' "${log}"
grep -q "Process 1: sh.elf (Ring 3)" "${log}"
grep -q "TypePHP-OS user shell" "${log}"
grep -q "Ring 3 confirmed" "${log}"
grep -Fq 'typephp-os:/$ ' "${log}"
grep -Fq 'typephp-os:/DOCS$ ' "${log}"
grep -q '^HELLO.TXT' "${log}"
cat "${log}"

@ -0,0 +1,11 @@
#include "syscall.h"
void _start(const char *argument)
{
const char *path = argument != 0 && argument[0] != '\0' ? argument : "/";
if (typephp_syscall(TYPEPHP_SYS_CHDIR, (long) path, 0, 0) != 0) {
typephp_write("cd: no such directory\n");
typephp_exit(1);
}
typephp_exit(0);
}

@ -0,0 +1,15 @@
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SECTIONS
{
. = USER_BASE + SIZEOF_HEADERS;
.text : { *(.text .text.*) }
.rodata : { *(.rodata .rodata.*) }
.data : { *(.data .data.*) }
.bss (NOLOAD) : { *(COMMON) *(.bss .bss.*) }
/DISCARD/ : { *(.comment) *(.eh_frame*) *(.note*) }
}

@ -0,0 +1,15 @@
#include "syscall.h"
void _start(const char *argument)
{
char entries[512];
const char *path = argument != 0 && argument[0] != '\0' ? argument : 0;
long length = typephp_syscall(
TYPEPHP_SYS_READDIR, (long) path, (long) entries, sizeof(entries));
if (length < 0) {
typephp_write("ls: cannot read directory\n");
typephp_exit(1);
}
typephp_write_bytes(entries, (size_t) length);
typephp_exit(0);
}

@ -0,0 +1,92 @@
#include "syscall.h"
static int string_equal(const char *left, const char *right)
{
while (*left != '\0' && *left == *right) {
++left;
++right;
}
return *left == *right;
}
static size_t read_line(char *buffer, size_t capacity)
{
size_t length = 0;
while (capacity > 1) {
char character = 0;
if (typephp_syscall(TYPEPHP_SYS_READ, 0, (long) &character, 1) != 1) {
continue;
}
if (character == '\r' || character == '\n') {
typephp_write("\n");
break;
}
if (character == 8 || character == 127) {
if (length != 0) {
--length;
typephp_write("\b \b");
}
continue;
}
if (character >= 32 && character < 127) {
buffer[length++] = character;
--capacity;
typephp_write_bytes(&character, 1);
}
}
buffer[length] = '\0';
return length;
}
static void show_prompt(void)
{
char cwd[16];
typephp_write("typephp-os:");
if (typephp_syscall(TYPEPHP_SYS_GETCWD, (long) cwd, sizeof(cwd), 0) > 0) {
typephp_write(cwd);
} else {
typephp_write("?");
}
typephp_write("$ ");
}
void _start(void)
{
char line[80];
unsigned short code_selector;
__asm__ volatile("mov %%cs, %0" : "=r"(code_selector));
if ((code_selector & 3u) != 3u) {
typephp_write("sh: Ring-3 transition failed\n");
typephp_exit(1);
}
typephp_write("TypePHP-OS user shell\n");
typephp_write("Ring 3 confirmed\n");
typephp_write("Commands: ls, cd <directory>\n");
for (;;) {
char *argument;
show_prompt();
if (read_line(line, sizeof(line)) == 0) {
continue;
}
argument = line;
while (*argument != '\0' && *argument != ' ') {
++argument;
}
if (*argument != '\0') {
*argument++ = '\0';
while (*argument == ' ') {
++argument;
}
}
if (string_equal(line, "ls") || string_equal(line, "cd")) {
if (typephp_syscall(TYPEPHP_SYS_EXEC,
(long) line, (long) argument, 0) < 0) {
typephp_write("sh: unable to execute command\n");
}
} else {
typephp_write("sh: command not found: ");
typephp_write(line);
typephp_write("\n");
}
}
}

@ -0,0 +1,57 @@
#ifndef TYPEPHP_OS_USER_SYSCALL_H
#define TYPEPHP_OS_USER_SYSCALL_H
typedef unsigned long size_t;
enum {
TYPEPHP_SYS_READ = 0,
TYPEPHP_SYS_WRITE = 1,
TYPEPHP_SYS_EXEC = 59,
TYPEPHP_SYS_EXIT = 60,
TYPEPHP_SYS_GETCWD = 79,
TYPEPHP_SYS_CHDIR = 80,
TYPEPHP_SYS_READDIR = 217,
};
static inline long typephp_syscall(
long number, long first, long second, long third)
{
register long rax __asm__("rax") = number;
register long rdi __asm__("rdi") = first;
register long rsi __asm__("rsi") = second;
register long rdx __asm__("rdx") = third;
__asm__ volatile("int $0x80"
: "+a"(rax)
: "D"(rdi), "S"(rsi), "d"(rdx)
: "rcx", "r11", "memory");
return rax;
}
static inline size_t typephp_strlen(const char *value)
{
size_t length = 0;
while (value[length] != '\0') {
++length;
}
return length;
}
static inline void typephp_write_bytes(const char *value, size_t length)
{
(void) typephp_syscall(TYPEPHP_SYS_WRITE, 1, (long) value, (long) length);
}
static inline void typephp_write(const char *value)
{
typephp_write_bytes(value, typephp_strlen(value));
}
static inline void typephp_exit(long status)
{
(void) typephp_syscall(TYPEPHP_SYS_EXIT, status, 0, 0);
for (;;) {
__asm__ volatile("pause");
}
}
#endif
Loading…
Cancel
Save