Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions CMDLINE_ARGS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Command Line Arguments Support for os.Args

## Overview

This implementation adds support for kernel command line arguments that are accessible via `os.Args` in the eggos kernel. Previously, `os.Args` only contained `["eggos"]`. Now it can include additional arguments passed via QEMU's `-append` parameter.

## Changes Made

### 1. Kernel Changes (`kernel/env.go`)

Modified the `prepareArgs()` function to:
- Reserve space for argc at the beginning
- Add arg0 ("eggos") as the first argument
- Call `putKernelArgs()` to parse and add command line arguments from multiboot
- Update argc to reflect the total count (1 + number of cmdline args)

**Key Change**: Command line arguments are now added to the args array (for `os.Args`) instead of the environment variables array.

### 2. Egg Tool Enhancement (`cmd/egg/cmd/run.go`)

Added a new `--append` / `-a` flag to the `egg run` command:
- Allows users to easily pass kernel arguments
- Automatically adds the `-append` parameter to QEMU

## Usage

### Method 1: Using the egg tool with --append flag

```bash
# Build and run with arguments
egg run kernel.elf --append "arg1 arg2 arg3"

# Or using the short flag
egg run kernel.elf -a "debug verbose level=3"
```

### Method 2: Using QEMU directly

```bash
# Set QEMU_OPTS environment variable
export QEMU_OPTS="-append 'arg1 arg2 arg3'"
egg run kernel.elf

# Or with mage
QEMU_OPTS="-append 'debug mode=test'" mage qemu
```

### Method 3: Direct QEMU invocation

```bash
qemu-system-x86_64 \
-kernel multiboot.elf \
-initrd kernel.elf \
-append "arg1 arg2 arg3" \
-m 256M \
-nographic
```

## Example

In your kernel application (e.g., `app/kmain/main.go`):

```go
package main

import (
"os"
_ "github.com/icexin/eggos"
"github.com/icexin/eggos/log"
)

func main() {
log.Infof("[runtime] os.Args: %v", os.Args)

// Process arguments
for i, arg := range os.Args {
log.Infof(" Args[%d]: %s", i, arg)
}
}
```

### Expected Output

When running with: `egg run kernel.elf -a "debug verbose level=3"`

```
[runtime] os.Args: [eggos debug verbose level=3]
Args[0]: eggos
Args[1]: debug
Args[2]: verbose
Args[3]: level=3
```

## Implementation Details

### Argument Parsing

The `putKernelArgs()` function in `kernel/env.go`:
1. Reads the command line string from multiboot info structure
2. Tokenizes the string by spaces using `strtok()`
3. Adds each token as a pointer to the args array
4. Returns the count of arguments parsed

### Memory Layout

The args are set up in memory following the standard Linux convention:
```
[argc]
[argv[0]] -> "eggos\0"
[argv[1]] -> "arg1\0"
[argv[2]] -> "arg2\0"
...
[NULL]
[envp[0]] -> "TERM=xterm\0"
[envp[1]] -> "GODEBUG=asyncpreemptoff=1\0"
[NULL]
[auxv...]
```

## Testing

### Test 1: No Arguments (Backward Compatibility)
```bash
egg run kernel.elf
# Expected: os.Args = ["eggos"]
```

### Test 2: Single Argument
```bash
egg run kernel.elf -a "debug"
# Expected: os.Args = ["eggos", "debug"]
```

### Test 3: Multiple Arguments
```bash
egg run kernel.elf -a "arg1 arg2 arg3"
# Expected: os.Args = ["eggos", "arg1", "arg2", "arg3"]
```

### Test 4: Arguments with Special Values
```bash
egg run kernel.elf -a "key=value mode=test level=3"
# Expected: os.Args = ["eggos", "key=value", "mode=test", "level=3"]
```

## Notes

- Arguments are space-separated
- The first argument (argv[0]) is always "eggos"
- Arguments are parsed from the multiboot command line field
- The implementation maintains backward compatibility - if no arguments are provided, os.Args contains only ["eggos"]

## Related Files

- `kernel/env.go` - Main implementation of argument parsing
- `cmd/egg/cmd/run.go` - Egg tool enhancement for --append flag
- `drivers/multiboot/info.go` - Multiboot info structure definition
- `boot/multiboot.h` - Multiboot specification header

## References

- GitHub Issue #70: kernel: add command line arguments for `os.Args`
- Multiboot Specification: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
88 changes: 88 additions & 0 deletions QUICK_START_CMDLINE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Quick Start: Command Line Arguments

## TL;DR

Pass arguments to your eggos kernel using the `-a` flag:

```bash
egg run kernel.elf -a "your arguments here"
```

Access them in your Go code:

```go
import "os"

func main() {
for i, arg := range os.Args {
println(i, arg)
}
}
```

## Quick Examples

### Example 1: Debug Mode
```bash
egg run kernel.elf -a "debug"
```

```go
// In your kernel code
if len(os.Args) > 1 && os.Args[1] == "debug" {
log.SetLevel(log.DebugLevel)
}
```

### Example 2: Configuration
```bash
egg run kernel.elf -a "mode=test timeout=30 verbose"
```

```go
// In your kernel code
config := parseArgs(os.Args[1:])
// config["mode"] = "test"
// config["timeout"] = "30"
```

### Example 3: Multiple Values
```bash
egg run kernel.elf -a "server1 server2 server3"
```

```go
// In your kernel code
servers := os.Args[1:] // ["server1", "server2", "server3"]
```

## Try the Example

```bash
cd app/examples/cmdline
egg run -a "hello world foo=bar test=123"
```

## Alternative Methods

### Using QEMU_OPTS
```bash
export QEMU_OPTS="-append 'arg1 arg2'"
egg run kernel.elf
```

### Using mage
```bash
QEMU_OPTS="-append 'debug mode=test'" mage qemu
```

## Notes

- Arguments are space-separated
- `os.Args[0]` is always "eggos"
- Your arguments start at `os.Args[1]`
- No arguments? `os.Args` = `["eggos"]`

## Need Help?

See `CMDLINE_ARGS.md` for detailed documentation.
13 changes: 12 additions & 1 deletion app/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,15 @@ This example shows how to add or modify syscall
``` sh
$ cd syscall
$ egg run
```
```

# command line arguments

This example demonstrates how to use command line arguments with os.Args

``` sh
$ cd cmdline
$ egg run -a "debug verbose level=3 mode=test"
```

The kernel will receive and display the command line arguments passed via the `-a` flag.
46 changes: 46 additions & 0 deletions app/examples/cmdline/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
"fmt"
"os"
"strings"

_ "github.com/icexin/eggos"
"github.com/icexin/eggos/app/sh"
"github.com/icexin/eggos/console"
"github.com/icexin/eggos/log"
)

func main() {
log.Infof("[cmdline example] Starting command line arguments demo")
log.Infof("[cmdline example] os.Args: %v", os.Args)
log.Infof("[cmdline example] Number of arguments: %d", len(os.Args))

w := console.Console()

// Print all arguments
fmt.Fprintf(w, "\n=== Command Line Arguments Demo ===\n")
fmt.Fprintf(w, "Total arguments: %d\n\n", len(os.Args))

for i, arg := range os.Args {
fmt.Fprintf(w, " Args[%d]: %s\n", i, arg)
}

// Parse key=value style arguments
fmt.Fprintf(w, "\n=== Parsed Key-Value Arguments ===\n")
for i, arg := range os.Args {
if i == 0 {
continue // Skip program name
}
if strings.Contains(arg, "=") {
parts := strings.SplitN(arg, "=", 2)
fmt.Fprintf(w, " %s = %s\n", parts[0], parts[1])
}
}

fmt.Fprintf(w, "\n=== Demo Complete ===\n")
fmt.Fprintf(w, "\nStarting shell...\n\n")

// Start the shell
sh.Bootstrap()
}
12 changes: 10 additions & 2 deletions cmd/egg/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
Expand Down Expand Up @@ -35,7 +35,8 @@ const (
)

var (
ports []string
ports []string
appendArgs string
)

// runCmd represents the run command
Expand Down Expand Up @@ -100,6 +101,12 @@ func runKernel(args []string) error {
runArgs = append(runArgs, "-netdev", "user,id=eth0"+portMapingArgs())
runArgs = append(runArgs, "-device", "e1000,netdev=eth0")
runArgs = append(runArgs, "-device", "isa-debug-exit")

// Add kernel command line arguments if specified
if appendArgs != "" {
runArgs = append(runArgs, "-append", appendArgs)
}

runArgs = append(runArgs, qemuArgs...)

cmd := exec.Command(qemu64, runArgs...)
Expand Down Expand Up @@ -158,4 +165,5 @@ func portMapingArgs() string {
func init() {
rootCmd.AddCommand(runCmd)
runCmd.Flags().StringSliceVarP(&ports, "port", "p", nil, "port mapping from host to kernel, format $host_port:$kernel_port")
runCmd.Flags().StringVarP(&appendArgs, "append", "a", "", "kernel command line arguments (passed to os.Args)")
}
15 changes: 11 additions & 4 deletions kernel/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func envput(pbuf *[]byte, v uintptr) uintptr {
}

// envptr used to alloc an *uintptr
//
//go:nosplit
func envptr(pbuf *[]byte) *uintptr {
return (*uintptr)(unsafe.Pointer(envput(pbuf, 0)))
Expand All @@ -38,16 +39,20 @@ func envdup(pbuf *[]byte, s string) uintptr {
func prepareArgs(sp uintptr) {
buf := sys.UnsafeBuffer(sp, 256)

var argc uintptr = 1
// put argc slot
envput(&buf, argc)
// reserve space for argc, will be filled later
argcPtr := envptr(&buf)

// put arg0
arg0 := envptr(&buf)

// put kernel command line arguments
cmdlineArgCount := putKernelArgs(&buf)

// end of args
envput(&buf, 0)

envTerm := envptr(&buf)
envGoDebug := envptr(&buf)
putKernelArgs(&buf)
// end of env
envput(&buf, 0)

Expand All @@ -57,6 +62,8 @@ func prepareArgs(sp uintptr) {
envput(&buf, linux.AT_NULL)
envput(&buf, 0)

// fill in argc (1 for arg0 + cmdline args)
*argcPtr = 1 + cmdlineArgCount
*arg0 = envdup(&buf, "eggos\x00")
*envTerm = envdup(&buf, "TERM=xterm\x00")
*envGoDebug = envdup(&buf, "GODEBUG=asyncpreemptoff=1\x00")
Expand Down