pyOS-kernel 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyos/__init__.py +22 -0
- pyos/boot/__init__.py +3 -0
- pyos/boot/bootloader.asm +174 -0
- pyos/builder.py +177 -0
- pyos/cli.py +286 -0
- pyos/compiler/__init__.py +8 -0
- pyos/compiler/assembler.py +180 -0
- pyos/compiler/codegen.py +238 -0
- pyos/drivers/__init__.py +8 -0
- pyos/drivers/keyboard.py +319 -0
- pyos/drivers/screen.py +310 -0
- pyos/emulator.py +195 -0
- pyos/interrupts/__init__.py +8 -0
- pyos/interrupts/handler.py +237 -0
- pyos/interrupts/idt.py +165 -0
- pyos/kernel.py +288 -0
- pyos/memory/__init__.py +8 -0
- pyos/memory/gdt.py +295 -0
- pyos/memory/manager.py +397 -0
- pyos/syscalls/__init__.py +7 -0
- pyos/syscalls/handler.py +179 -0
- pyos_kernel-0.1.0.dist-info/METADATA +323 -0
- pyos_kernel-0.1.0.dist-info/RECORD +27 -0
- pyos_kernel-0.1.0.dist-info/WHEEL +5 -0
- pyos_kernel-0.1.0.dist-info/entry_points.txt +2 -0
- pyos_kernel-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyos_kernel-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS Assembler - Assembly to Machine Code
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AssemblerError(Exception):
|
|
13
|
+
"""Raised when assembly fails."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Assembler:
|
|
18
|
+
"""
|
|
19
|
+
Assembles x86/x86_64 Assembly code to machine code.
|
|
20
|
+
|
|
21
|
+
Uses NASM (Netwide Assembler) for assembly.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, arch: str = "x86"):
|
|
25
|
+
"""
|
|
26
|
+
Initialize assembler.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
arch: Target architecture ("x86" or "x86_64")
|
|
30
|
+
"""
|
|
31
|
+
self.arch = arch
|
|
32
|
+
self.nasm_path = self._find_nasm()
|
|
33
|
+
|
|
34
|
+
def _find_nasm(self) -> str:
|
|
35
|
+
"""Find NASM executable."""
|
|
36
|
+
# Try common locations
|
|
37
|
+
paths = [
|
|
38
|
+
"nasm",
|
|
39
|
+
"/usr/bin/nasm",
|
|
40
|
+
"/usr/local/bin/nasm",
|
|
41
|
+
"C:\\Program Files\\NASM\\nasm.exe",
|
|
42
|
+
"C:\\NASM\\nasm.exe",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
for path in paths:
|
|
46
|
+
try:
|
|
47
|
+
result = subprocess.run(
|
|
48
|
+
[path, "-v"],
|
|
49
|
+
capture_output=True,
|
|
50
|
+
text=True,
|
|
51
|
+
)
|
|
52
|
+
if result.returncode == 0:
|
|
53
|
+
return path
|
|
54
|
+
except FileNotFoundError:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
return "nasm" # Hope it's in PATH
|
|
58
|
+
|
|
59
|
+
def assemble(self, asm_code: str, output_format: str = "bin") -> bytes:
|
|
60
|
+
"""
|
|
61
|
+
Assemble code to machine code.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
asm_code: Assembly source code
|
|
65
|
+
output_format: Output format (bin, elf, elf64)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Binary machine code.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
AssemblerError: If assembly fails.
|
|
72
|
+
"""
|
|
73
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
74
|
+
asm_file = Path(tmpdir) / "kernel.asm"
|
|
75
|
+
out_file = Path(tmpdir) / "kernel.bin"
|
|
76
|
+
|
|
77
|
+
# Write assembly to file
|
|
78
|
+
asm_file.write_text(asm_code)
|
|
79
|
+
|
|
80
|
+
# Determine format
|
|
81
|
+
if self.arch == "x86_64":
|
|
82
|
+
fmt = "elf64" if output_format == "elf" else output_format
|
|
83
|
+
else:
|
|
84
|
+
fmt = "elf32" if output_format == "elf" else output_format
|
|
85
|
+
|
|
86
|
+
# Run NASM
|
|
87
|
+
cmd = [
|
|
88
|
+
self.nasm_path,
|
|
89
|
+
"-f", fmt,
|
|
90
|
+
"-o", str(out_file),
|
|
91
|
+
str(asm_file),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
result = subprocess.run(
|
|
95
|
+
cmd,
|
|
96
|
+
capture_output=True,
|
|
97
|
+
text=True,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if result.returncode != 0:
|
|
101
|
+
raise AssemblerError(f"NASM failed:\n{result.stderr}")
|
|
102
|
+
|
|
103
|
+
# Read binary output
|
|
104
|
+
return out_file.read_bytes()
|
|
105
|
+
|
|
106
|
+
def assemble_to_file(self, asm_code: str, output_path: str, output_format: str = "bin") -> str:
|
|
107
|
+
"""
|
|
108
|
+
Assemble code and write to file.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
asm_code: Assembly source code
|
|
112
|
+
output_path: Output file path
|
|
113
|
+
output_format: Output format
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Path to output file.
|
|
117
|
+
"""
|
|
118
|
+
binary = self.assemble(asm_code, output_format)
|
|
119
|
+
|
|
120
|
+
with open(output_path, "wb") as f:
|
|
121
|
+
f.write(binary)
|
|
122
|
+
|
|
123
|
+
return output_path
|
|
124
|
+
|
|
125
|
+
def link(self, object_files: list, output_path: str) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Link object files into executable.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
object_files: List of object file paths
|
|
131
|
+
output_path: Output executable path
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Path to linked executable.
|
|
135
|
+
"""
|
|
136
|
+
# Use ld for linking
|
|
137
|
+
cmd = ["ld", "-m"]
|
|
138
|
+
|
|
139
|
+
if self.arch == "x86_64":
|
|
140
|
+
cmd.append("elf_x86_64")
|
|
141
|
+
else:
|
|
142
|
+
cmd.append("elf_i386")
|
|
143
|
+
|
|
144
|
+
cmd.extend(["-T", "linker.ld"])
|
|
145
|
+
cmd.extend(["-o", output_path])
|
|
146
|
+
cmd.extend(object_files)
|
|
147
|
+
|
|
148
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
149
|
+
|
|
150
|
+
if result.returncode != 0:
|
|
151
|
+
raise AssemblerError(f"Linker failed:\n{result.stderr}")
|
|
152
|
+
|
|
153
|
+
return output_path
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def is_nasm_available() -> bool:
|
|
157
|
+
"""Check if NASM is available."""
|
|
158
|
+
try:
|
|
159
|
+
result = subprocess.run(
|
|
160
|
+
["nasm", "-v"],
|
|
161
|
+
capture_output=True,
|
|
162
|
+
)
|
|
163
|
+
return result.returncode == 0
|
|
164
|
+
except FileNotFoundError:
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def get_nasm_version() -> Optional[str]:
|
|
169
|
+
"""Get NASM version string."""
|
|
170
|
+
try:
|
|
171
|
+
result = subprocess.run(
|
|
172
|
+
["nasm", "-v"],
|
|
173
|
+
capture_output=True,
|
|
174
|
+
text=True,
|
|
175
|
+
)
|
|
176
|
+
if result.returncode == 0:
|
|
177
|
+
return result.stdout.strip()
|
|
178
|
+
except FileNotFoundError:
|
|
179
|
+
pass
|
|
180
|
+
return None
|
pyos/compiler/codegen.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS Code Generator - Generates Assembly from Python
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, List, Dict, Any
|
|
6
|
+
from ..drivers.screen import Screen
|
|
7
|
+
from ..drivers.keyboard import Keyboard
|
|
8
|
+
from ..memory.manager import Memory
|
|
9
|
+
from ..memory.gdt import GDT
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..kernel import Kernel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CodeGenerator:
|
|
16
|
+
"""
|
|
17
|
+
Generates x86/x86_64 Assembly code from Python kernel code.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, kernel: 'Kernel'):
|
|
21
|
+
self.kernel = kernel
|
|
22
|
+
self.arch = kernel.config.arch.value
|
|
23
|
+
self.asm_lines: List[str] = []
|
|
24
|
+
self.string_counter = 0
|
|
25
|
+
self.label_counter = 0
|
|
26
|
+
self._strings_to_emit = []
|
|
27
|
+
self._func_operations = {} # Operations per function
|
|
28
|
+
|
|
29
|
+
def generate(self) -> str:
|
|
30
|
+
"""Generate complete Assembly code."""
|
|
31
|
+
self.asm_lines = []
|
|
32
|
+
self.string_counter = 0
|
|
33
|
+
self._strings_to_emit = []
|
|
34
|
+
self._func_operations = {}
|
|
35
|
+
|
|
36
|
+
# Execute each boot function separately and capture its operations
|
|
37
|
+
for boot_func in self.kernel._boot_functions:
|
|
38
|
+
Screen._reset()
|
|
39
|
+
boot_func.func()
|
|
40
|
+
ops = Screen._get_operations().copy()
|
|
41
|
+
self._func_operations[boot_func.name] = ops
|
|
42
|
+
|
|
43
|
+
# Collect strings from this function
|
|
44
|
+
for op in ops:
|
|
45
|
+
if op["type"] == "print" and "text" in op:
|
|
46
|
+
label = f"str_{self.string_counter}"
|
|
47
|
+
self.string_counter += 1
|
|
48
|
+
text = op["text"]
|
|
49
|
+
self._strings_to_emit.append((label, text))
|
|
50
|
+
op["_string_label"] = label
|
|
51
|
+
|
|
52
|
+
# Header
|
|
53
|
+
self._emit_header()
|
|
54
|
+
|
|
55
|
+
# Text section
|
|
56
|
+
self._emit_text_section()
|
|
57
|
+
|
|
58
|
+
return '\n'.join(self.asm_lines)
|
|
59
|
+
|
|
60
|
+
def _emit(self, line: str) -> None:
|
|
61
|
+
"""Emit a line of assembly."""
|
|
62
|
+
self.asm_lines.append(line)
|
|
63
|
+
|
|
64
|
+
def _emit_header(self) -> None:
|
|
65
|
+
"""Emit assembly header."""
|
|
66
|
+
self._emit("; pyOS Generated Kernel")
|
|
67
|
+
self._emit(f"; Architecture: {self.arch}")
|
|
68
|
+
self._emit("; Generated by pyOS Compiler")
|
|
69
|
+
self._emit("")
|
|
70
|
+
|
|
71
|
+
if self.arch == "x86":
|
|
72
|
+
self._emit("[BITS 32]")
|
|
73
|
+
else:
|
|
74
|
+
self._emit("[BITS 64]")
|
|
75
|
+
|
|
76
|
+
self._emit("[ORG 0x1000]")
|
|
77
|
+
self._emit("")
|
|
78
|
+
|
|
79
|
+
def _emit_text_section(self) -> None:
|
|
80
|
+
"""Emit text section with code."""
|
|
81
|
+
self._emit("; ==================")
|
|
82
|
+
self._emit("; Kernel Entry Point")
|
|
83
|
+
self._emit("; ==================")
|
|
84
|
+
self._emit("")
|
|
85
|
+
self._emit("_start:")
|
|
86
|
+
self._emit(" mov esp, 0x90000")
|
|
87
|
+
|
|
88
|
+
# Call boot functions in correct order (sorted by priority, lower first)
|
|
89
|
+
sorted_funcs = sorted(self.kernel._boot_functions, key=lambda x: x.priority)
|
|
90
|
+
|
|
91
|
+
self._emit("")
|
|
92
|
+
self._emit(" ; Call boot functions")
|
|
93
|
+
for boot_func in sorted_funcs:
|
|
94
|
+
self._emit(f" call {boot_func.name}")
|
|
95
|
+
|
|
96
|
+
# Main loop
|
|
97
|
+
self._emit("")
|
|
98
|
+
self._emit(" ; Main loop - halt CPU")
|
|
99
|
+
self._emit(".main_loop:")
|
|
100
|
+
self._emit(" hlt")
|
|
101
|
+
self._emit(" jmp .main_loop")
|
|
102
|
+
|
|
103
|
+
# Generate boot functions
|
|
104
|
+
self._emit("")
|
|
105
|
+
self._generate_boot_functions()
|
|
106
|
+
|
|
107
|
+
# Generate helper functions
|
|
108
|
+
self._emit("")
|
|
109
|
+
self._generate_screen_functions()
|
|
110
|
+
self._generate_keyboard_functions()
|
|
111
|
+
|
|
112
|
+
def _generate_boot_functions(self) -> None:
|
|
113
|
+
"""Generate code for boot functions."""
|
|
114
|
+
for boot_func in self.kernel._boot_functions:
|
|
115
|
+
self._emit(f"; Function: {boot_func.name}")
|
|
116
|
+
self._emit(f"{boot_func.name}:")
|
|
117
|
+
self._emit(" push ebp")
|
|
118
|
+
self._emit(" mov ebp, esp")
|
|
119
|
+
|
|
120
|
+
# Get operations for THIS function only
|
|
121
|
+
ops = self._func_operations.get(boot_func.name, [])
|
|
122
|
+
|
|
123
|
+
# Generate code for screen operations
|
|
124
|
+
for op in ops:
|
|
125
|
+
if op["type"] == "clear":
|
|
126
|
+
self._emit(" call screen_clear")
|
|
127
|
+
elif op["type"] == "print":
|
|
128
|
+
label = op.get("_string_label", "str_0")
|
|
129
|
+
row = op.get("row", 0)
|
|
130
|
+
col = op.get("col", 0)
|
|
131
|
+
fg = op.get("foreground", 15)
|
|
132
|
+
bg = op.get("background", 0)
|
|
133
|
+
color = (bg << 4) | fg
|
|
134
|
+
self._emit(f" push dword {color}")
|
|
135
|
+
self._emit(f" push dword {col}")
|
|
136
|
+
self._emit(f" push dword {row}")
|
|
137
|
+
self._emit(f" push dword {label}")
|
|
138
|
+
self._emit(" call screen_print")
|
|
139
|
+
self._emit(" add esp, 16")
|
|
140
|
+
|
|
141
|
+
self._emit(" pop ebp")
|
|
142
|
+
self._emit(" ret")
|
|
143
|
+
self._emit("")
|
|
144
|
+
|
|
145
|
+
def _generate_screen_functions(self) -> None:
|
|
146
|
+
"""Generate VGA screen functions."""
|
|
147
|
+
self._emit("; ==================")
|
|
148
|
+
self._emit("; Screen Functions")
|
|
149
|
+
self._emit("; ==================")
|
|
150
|
+
self._emit("VGA_MEMORY equ 0xB8000")
|
|
151
|
+
self._emit("VGA_WIDTH equ 80")
|
|
152
|
+
self._emit("VGA_HEIGHT equ 25")
|
|
153
|
+
self._emit("")
|
|
154
|
+
|
|
155
|
+
# screen_clear
|
|
156
|
+
self._emit("screen_clear:")
|
|
157
|
+
self._emit(" push ebp")
|
|
158
|
+
self._emit(" mov ebp, esp")
|
|
159
|
+
self._emit(" push edi")
|
|
160
|
+
self._emit(" push eax")
|
|
161
|
+
self._emit(" push ecx")
|
|
162
|
+
self._emit("")
|
|
163
|
+
self._emit(" mov edi, VGA_MEMORY")
|
|
164
|
+
self._emit(" mov ecx, VGA_WIDTH * VGA_HEIGHT")
|
|
165
|
+
self._emit(" mov ax, 0x0720")
|
|
166
|
+
self._emit(" rep stosw")
|
|
167
|
+
self._emit("")
|
|
168
|
+
self._emit(" pop ecx")
|
|
169
|
+
self._emit(" pop eax")
|
|
170
|
+
self._emit(" pop edi")
|
|
171
|
+
self._emit(" pop ebp")
|
|
172
|
+
self._emit(" ret")
|
|
173
|
+
self._emit("")
|
|
174
|
+
|
|
175
|
+
# screen_print (string, row, col, color)
|
|
176
|
+
self._emit("screen_print:")
|
|
177
|
+
self._emit(" push ebp")
|
|
178
|
+
self._emit(" mov ebp, esp")
|
|
179
|
+
self._emit(" push esi")
|
|
180
|
+
self._emit(" push edi")
|
|
181
|
+
self._emit(" push eax")
|
|
182
|
+
self._emit(" push ebx")
|
|
183
|
+
self._emit("")
|
|
184
|
+
self._emit(" mov esi, [ebp+8]")
|
|
185
|
+
self._emit(" mov eax, [ebp+12]")
|
|
186
|
+
self._emit(" mov ebx, VGA_WIDTH * 2")
|
|
187
|
+
self._emit(" mul ebx")
|
|
188
|
+
self._emit(" mov edi, [ebp+16]")
|
|
189
|
+
self._emit(" shl edi, 1")
|
|
190
|
+
self._emit(" add edi, eax")
|
|
191
|
+
self._emit(" add edi, VGA_MEMORY")
|
|
192
|
+
self._emit(" mov ah, [ebp+20]")
|
|
193
|
+
self._emit("")
|
|
194
|
+
self._emit(".sp_loop:")
|
|
195
|
+
self._emit(" lodsb")
|
|
196
|
+
self._emit(" test al, al")
|
|
197
|
+
self._emit(" jz .sp_done")
|
|
198
|
+
self._emit(" stosw")
|
|
199
|
+
self._emit(" jmp .sp_loop")
|
|
200
|
+
self._emit("")
|
|
201
|
+
self._emit(".sp_done:")
|
|
202
|
+
self._emit(" pop ebx")
|
|
203
|
+
self._emit(" pop eax")
|
|
204
|
+
self._emit(" pop edi")
|
|
205
|
+
self._emit(" pop esi")
|
|
206
|
+
self._emit(" pop ebp")
|
|
207
|
+
self._emit(" ret")
|
|
208
|
+
self._emit("")
|
|
209
|
+
|
|
210
|
+
def _generate_keyboard_functions(self) -> None:
|
|
211
|
+
"""Generate keyboard functions."""
|
|
212
|
+
self._emit("; ==================")
|
|
213
|
+
self._emit("; Keyboard Functions")
|
|
214
|
+
self._emit("; ==================")
|
|
215
|
+
self._emit("KEYBOARD_DATA_PORT equ 0x60")
|
|
216
|
+
self._emit("KEYBOARD_STATUS_PORT equ 0x64")
|
|
217
|
+
self._emit("")
|
|
218
|
+
|
|
219
|
+
self._emit("keyboard_read_scancode:")
|
|
220
|
+
self._emit(" push ebp")
|
|
221
|
+
self._emit(" mov ebp, esp")
|
|
222
|
+
self._emit(".kb_wait:")
|
|
223
|
+
self._emit(" in al, KEYBOARD_STATUS_PORT")
|
|
224
|
+
self._emit(" test al, 1")
|
|
225
|
+
self._emit(" jz .kb_wait")
|
|
226
|
+
self._emit(" in al, KEYBOARD_DATA_PORT")
|
|
227
|
+
self._emit(" movzx eax, al")
|
|
228
|
+
self._emit(" pop ebp")
|
|
229
|
+
self._emit(" ret")
|
|
230
|
+
self._emit("")
|
|
231
|
+
|
|
232
|
+
# Emit strings at the end
|
|
233
|
+
self._emit("; ==================")
|
|
234
|
+
self._emit("; Data Section")
|
|
235
|
+
self._emit("; ==================")
|
|
236
|
+
for label, text in self._strings_to_emit:
|
|
237
|
+
escaped = text.replace('\\', '\\\\').replace('"', '\\"')
|
|
238
|
+
self._emit(f'{label}: db "{escaped}", 0')
|