e9studio-gui.com v0.1.0 release binary#8
e9studio-gui.com v0.1.0 release binary#8ludoplex wants to merge 20 commits intoclaude/cosmopolitan-ide-wasm-port-gSYQgfrom
Conversation
* Add e9studio GUI framework (cosmo-teditor foundation) Initial GUI framework for e9studio with: - Platform detection (Windows/Linux/macOS/BSD) - Backend auto-detection (Win32/X11/Cocoa/TUI) - TUI fallback when GUI unavailable - Plugin system architecture - Panel system for dockable views - Configuration system (INI-based) Design inspired by teditor's extensibility philosophy: - Simple core with plugin extensions - Cross-platform via Cosmopolitan Libc - Graceful degradation to TUI mode Files added: - gui/TEDITOR_DESIGN.md - Architecture documentation - gui/e9studio_gui.h - Public API header - gui/e9studio_gui.c - Core implementation (stubs) Next steps: - Implement Win32/X11/Cocoa backends - Wire up to e9analysis for disasm/decompile views - Add E9Scanner integration * Integrate tedit-cosmo editor with GUI framework and WASM build support - Add tedit-cosmo inspired application core (e9studio_app.c) - Gap buffer implementation for efficient text editing - Editor state with cursor, selection, and history tracking - File operations (load/save) with language detection - Menu system with INI configuration - Add unified entry point (e9studio_gui_main.c) - Auto-detect GUI/TUI/CLI mode based on environment - Self-test diagnostics for validation - Command-line argument parsing with help/version - Add CLI platform backend (e9gui_cli.c) - Text-based interface for binary analysis commands - Build system integration with variable substitution - Placeholder hooks for analysis engine integration - Add e9studio_tedit.h integration header - Bridge between tedit-cosmo and e9studio analysis - Application state with editor management - Platform abstraction interface - Add WASM payload build support (Makefile.wasm) - Build WAT files using wasm-as from binaryen - Optimize WASM with wasm-opt - Example payload generation - Update Makefile.e9studio - Add gui/cli build targets - Add wasm/binaryen targets - Include GUI sources with proper flags Tested: make gui passes with native gcc, self-test passes all checks. * Fix memory safety and portability issues from Copilot review Address code review feedback: Memory Safety: - Add null-check protection for all realloc calls in panel, editor, and menu array growth to prevent memory leaks on allocation failure - Store realloc results in temporary variables before assignment - Replace unsafe strcpy with memcpy/strncpy for bounded string copies - Add explicit null-termination after sscanf parsing Portability: - Replace hardcoded /home/user/binaryen path with dynamic detection that checks ../binaryen (sibling), /opt/binaryen, then falls back to system PATH - Update both Makefile.e9studio and Makefile.wasm for consistent binaryen path handling All tests pass after changes. * Address second round of Copilot review feedback String Safety: - Add explicit null-termination after all strncpy calls to ensure strings are properly terminated even when source equals buffer size - Affected: window titles, panel IDs/titles, file paths, menu labels, menu commands, build configuration strings Error Handling: - Change buffer_ensure_gap to return int (0 success, -1 failure) instead of silently failing on allocation errors - Update buffer_insert to propagate allocation failures - Fix e9editor_get_selection to return initialized empty string instead of uninitialized memory for stub implementation All self-tests pass after changes. --------- Co-authored-by: Claude <noreply@anthropic.com>
Cosmopolitan Libc portable executable: - Runs on Linux, macOS, Windows, BSD - CLI mode with binary analysis commands - Self-tests: 7/7 passing Co-authored-by: Claude <noreply@anthropic.com>
Reviewer's GuideIntroduces the initial E9Studio GUI/CLI framework and application core for the 0.1.0 e9studio-gui.com release, including editor buffer management, app state, GUI/TUI abstraction, CLI backend, and supporting headers and docs. Sequence diagram for unified mode selection and CLI startup in e9studio_gui_mainsequenceDiagram
participant User
participant Main as e9studio_gui_main
participant Args as parse_args
participant TuiCheck as e9gui_should_use_tui
participant Backend as e9gui_detect_backend
participant App as E9AppState
participant Platform as e9platform_init/run/shutdown
User->>Main: start e9studio-gui.com(argc, argv)
Main->>Args: parse_args(argc, argv)
Args-->>Main: E9StartupOptions opts
alt opts.self_test
Main->>Main: run_self_test()
Main-->>User: exit
else normal_start
alt opts.mode == MODE_AUTO
Main->>TuiCheck: e9gui_should_use_tui(argc, argv)
TuiCheck-->>Main: use_tui?
alt use_tui
Main->>Main: mode = MODE_TUI
else gui_possible
Main->>Backend: e9gui_detect_backend()
Backend-->>Main: backend
alt backend == E9_BACKEND_TUI
Main->>Main: mode = MODE_CLI
else
Main->>Main: mode = MODE_GUI
end
end
else explicit_mode
Main->>Main: mode = opts.mode
end
alt mode == MODE_TUI
Main->>Main: e9studio_tui_main(argc, argv)
Main-->>User: TUI session
else mode == MODE_GUI
Main->>Main: create E9Window with config
Main->>Main: if backend TUI fallback
alt GUI available
Main->>Main: e9gui_main_loop(win, callback, userdata)
Main-->>User: GUI session
else GUI not available
Main->>Main: mode = MODE_CLI
end
end
alt mode == MODE_CLI
Main->>App: e9app_init(&app)
alt opts.config_path
Main->>App: e9build_load_config(&app.build, config_path)
end
alt opts.menu_path
Main->>App: e9menu_load_ini(&app.menus, menu_path)
end
alt opts.binary_path
Main->>App: e9app_load_binary(&app, binary_path)
end
Main->>Platform: e9platform_init(&app)
Platform-->>Main: 0 or error
Main->>Platform: e9platform_run(&app)
Platform-->>Main: result
Main->>Platform: e9platform_shutdown(&app)
Main->>App: e9app_shutdown(&app)
Main-->>User: CLI session finished
end
end
Class diagram for E9Studio application, editor, and buffer coreclassDiagram
class E9Buffer {
+char* data
+size_t size
+size_t capacity
+size_t gap_start
+size_t gap_end
+buffer_create()
+buffer_destroy()
+buffer_insert(pos, text, len)
+buffer_delete(pos, len)
+buffer_get_text(out, max)
}
class E9HistoryEntry {
+int type
+size_t position
+char* text
+size_t length
+E9HistoryEntry* next
+E9HistoryEntry* prev
}
class E9Language {
<<enum>>
E9_LANG_NONE
E9_LANG_C
E9_LANG_ASM_X86
E9_LANG_ASM_ARM
E9_LANG_PATCH
E9_LANG_HEX
E9_LANG_INI
}
class E9EditorState {
+E9Buffer* buffer
+E9HistoryEntry* history
+E9HistoryEntry* history_pos
+char file_path[260]
+size_t cursor_line
+size_t cursor_col
+size_t selection_start
+size_t selection_end
+E9Language language
+int dirty
+int readonly
+int history_enabled
+e9editor_set_text(text, len)
+e9editor_get_text(buf, max)
+e9editor_get_length()
+e9editor_insert(pos, text, len)
+e9editor_delete(pos, len)
+e9editor_undo()
+e9editor_redo()
+e9editor_select_all()
+e9editor_get_selection(len)
+e9editor_goto_line(line)
+e9editor_get_cursor_pos(line, col)
+e9editor_load_file(path)
+e9editor_save_file(path)
}
class E9BuildConfig {
+char build_cmd[512]
+char run_cmd[512]
+char clean_cmd[512]
+char compiler[128]
+char flags[256]
+e9build_load_config(path)
+e9build_run_command(cmd)
}
class E9GuiMenuItem {
+char label[64]
+char command[256]
+char shortcut[16]
+bool enabled
+bool checked
+void (*callback)(void*)
+void* userdata
}
class E9GuiMenu {
+char label[64]
+E9GuiMenuItem* items
+size_t item_count
+size_t item_capacity
}
class E9MenuSet {
+E9GuiMenu* menus
+size_t menu_count
+size_t menu_capacity
+e9menu_load_ini(path)
+e9menu_free()
+e9menu_substitute_vars(out, out_size, cmd, file_path, exe_dir)
}
class E9Panel {
+char id[64]
+char title[128]
+E9PanelType type
+E9DockPosition dock
+E9Rect rect
+bool visible
+char* text_content
+size_t text_capacity
+E9Window* window
+e9gui_panel_set_title(title)
+e9gui_panel_set_dock(dock)
+e9gui_panel_set_visible(visible)
+e9gui_panel_set_text(text)
+e9gui_panel_get_text()
}
class E9AppState {
+E9EditorState** editors
+size_t editor_count
+size_t editor_capacity
+size_t active_editor
+E9BuildConfig build
+E9MenuSet menus
+char exe_dir[260]
+char config_dir[260]
+int running
+int gui_mode
+E9BinaryContext* binary
+E9Function* current_function
+E9Panel* panel_editor
+E9Panel* panel_disasm
+E9Panel* panel_decompile
+E9Panel* panel_hex
+E9Panel* panel_console
+E9Panel* panel_symbols
+E9Panel* panel_functions
+e9app_init()
+e9app_shutdown()
+e9app_new_editor()
+e9app_get_active_editor()
+e9app_close_editor(index)
+e9app_open_file(path)
+e9app_save_file(path)
+e9app_load_binary(path)
+e9app_show_function(func)
+e9app_show_decompile(func)
+e9app_show_hex(addr, size)
+e9app_show_symbols()
+e9app_show_functions()
+e9app_goto_address(addr)
+e9app_console_print(fmt, ...)
+e9app_console_clear()
}
class E9PanelType {
<<enum>>
E9_PANEL_EDITOR
E9_PANEL_DISASM
E9_PANEL_DECOMPILE
E9_PANEL_HEX
E9_PANEL_CONSOLE
E9_PANEL_PROJECTS
E9_PANEL_SYMBOLS
E9_PANEL_FUNCTIONS
E9_PANEL_XREFS
E9_PANEL_SCANNER
E9_PANEL_CUSTOM
}
class E9DockPosition {
<<enum>>
E9_DOCK_NONE
E9_DOCK_LEFT
E9_DOCK_RIGHT
E9_DOCK_TOP
E9_DOCK_BOTTOM
E9_DOCK_CENTER
E9_DOCK_FLOATING
}
class E9Rect {
+int x
+int y
+int width
+int height
}
class E9BinaryContext {
<<from analysis engine>>
}
class E9Function {
<<from analysis engine>>
+char* name
}
%% Relationships
E9EditorState --> E9Buffer : uses
E9HistoryEntry --> E9HistoryEntry : next/prev
E9EditorState --> E9HistoryEntry : history list
E9AppState "1" --> "*" E9EditorState : editors
E9AppState --> E9BuildConfig : build
E9AppState --> E9MenuSet : menus
E9AppState --> E9Panel : panels
E9AppState --> E9BinaryContext : binary
E9AppState --> E9Function : current_function
E9MenuSet "1" --> "*" E9GuiMenu : menus
E9GuiMenu "1" --> "*" E9GuiMenuItem : items
E9Panel --> E9PanelType : type
E9Panel --> E9DockPosition : dock
E9Panel --> E9Rect : rect
Class diagram for E9Studio GUI framework, platform, and plugin systemclassDiagram
class E9Platform {
<<enum>>
E9_PLATFORM_UNKNOWN
E9_PLATFORM_WINDOWS
E9_PLATFORM_LINUX
E9_PLATFORM_MACOS
E9_PLATFORM_FREEBSD
E9_PLATFORM_OPENBSD
E9_PLATFORM_NETBSD
}
class E9Backend {
<<enum>>
E9_BACKEND_NONE
E9_BACKEND_WIN32
E9_BACKEND_X11
E9_BACKEND_COCOA
E9_BACKEND_FRAMEBUFFER
E9_BACKEND_TUI
}
class E9WindowConfig {
+const char* title
+int width
+int height
+bool resizable
+bool fullscreen
+E9Backend backend
}
class E9Window {
+char title[256]
+int width
+int height
+bool should_close
+E9Backend backend
+void* native_handle
+E9Panel** panels
+size_t panel_count
+size_t panel_capacity
+e9gui_create_window(config)
+e9gui_destroy_window()
+e9gui_window_should_close()
+e9gui_window_set_title(title)
+e9gui_window_get_size(width, height)
+e9gui_window_get_backend()
}
class E9EventType {
<<enum>>
E9_EVENT_NONE
E9_EVENT_KEY_DOWN
E9_EVENT_KEY_UP
E9_EVENT_MOUSE_DOWN
E9_EVENT_MOUSE_UP
E9_EVENT_MOUSE_MOVE
E9_EVENT_MOUSE_WHEEL
E9_EVENT_RESIZE
E9_EVENT_CLOSE
E9_EVENT_FOCUS
E9_EVENT_BLUR
E9_EVENT_CUSTOM
}
class E9Event {
+E9EventType type
+key
+mouse
+wheel
+resize
+void* custom_data
}
class E9Color {
+uint8_t r
+uint8_t g
+uint8_t b
+uint8_t a
}
class E9PluginInfo {
+int api_version
+const char* name
+const char* version
+const char* author
+const char* description
+int (*init)()
+void (*shutdown)()
+void (*on_menu_build)(E9Menu*)
+void (*on_panel_create)(E9Panel*)
+void (*on_frame)(E9Window*)
+void (*on_binary_load)(void*)
+void (*on_function_select)(void*)
+void (*on_text_change)(E9Editor*, int, const char*)
}
class E9Config {
+char dummy
+e9gui_config_load(path)
+e9gui_config_save(path)
+e9gui_config_free()
+e9gui_config_get_string(section, key, default_val)
+e9gui_config_get_int(section, key, default_val)
+e9gui_config_get_bool(section, key, default_val)
+e9gui_config_set_string(section, key, value)
+e9gui_config_set_int(section, key, value)
+e9gui_config_set_bool(section, key, value)
}
class E9GuiFrameworkAPI {
+e9gui_get_platform()
+e9gui_detect_backend()
+e9gui_platform_name(platform)
+e9gui_backend_name(backend)
+e9gui_version()
+e9gui_get_error()
+e9gui_clear_error()
+e9gui_should_use_tui(argc, argv)
+e9gui_poll_event(win, event)
+e9gui_begin_frame(win)
+e9gui_end_frame(win)
+e9gui_main_loop(win, callback, userdata)
+e9gui_set_color(win, color)
+e9gui_draw_rect(win, rect, filled)
+e9gui_draw_line(win, p1, p2)
+e9gui_draw_text(win, x, y, text)
+e9gui_set_font(win, name, size)
+e9gui_get_text_size(win, text, width, height)
+e9gui_create_menu(label)
+e9gui_menu_add_item(menu, label, shortcut)
+e9gui_menu_add_submenu(menu, label)
+e9gui_menu_add_separator(menu)
+e9gui_menuitem_set_callback(item, callback, userdata)
+e9gui_menuitem_set_enabled(item, enabled)
+e9gui_menuitem_set_checked(item, checked)
+e9gui_register_plugin(plugin)
+e9gui_load_plugin_file(path)
+e9gui_unload_plugin(name)
+e9gui_get_plugin(name)
+e9gui_file_dialog(win, flags, filter, default_path)
+e9gui_message_box(win, title, message, type)
}
class E9studio_gui_main_entry {
+e9studio_gui_main(argc, argv)
}
%% Relationships
E9Window --> E9WindowConfig : created_from
E9Window "1" --> "*" E9Panel : panels
E9Window --> E9Backend : backend
E9GuiFrameworkAPI ..> E9Window : uses
E9GuiFrameworkAPI ..> E9Panel : uses
E9GuiFrameworkAPI ..> E9Config : uses
E9GuiFrameworkAPI ..> E9PluginInfo : manages
E9studio_gui_main_entry ..> E9GuiFrameworkAPI : calls
E9studio_gui_main_entry ..> E9platform_API : selects_mode
class E9platform_API {
+e9platform_init(E9AppState*)
+e9platform_run(E9AppState*)
+e9platform_shutdown(E9AppState*)
+e9platform_open_file_dialog(path, max, filter)
+e9platform_save_file_dialog(path, max, filter)
+e9platform_folder_dialog(path, max, title)
+e9platform_message_box(title, msg, type)
+e9platform_clipboard_set(text)
+e9platform_clipboard_get()
+e9platform_open_url(url)
+e9platform_run_external(cmd)
}
E9platform_API ..> E9AppState : operates_on
E9platform_API ..> E9EditorState : via_app
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 5 issues, and left some high level feedback:
- Several editor operations ignore allocation failures from the gap buffer helpers: for example,
e9editor_set_text,e9editor_insert, ande9editor_load_filecallbuffer_insert()without checking its-1error return, which can leave the editor in a partially updated or inconsistent state; consider propagating errors and failing the operation cleanly when allocation fails. - In
e9menu_substitute_vars, the{p}(project directory) expansion computeslenas1when no path separator is found, which will copy the first character of the file path instead of using the current directory; you likely intended to fall back to.when there is no directory component, so the no-separator case should setlento0and hit the.branch. - Also in
e9menu_substitute_vars, when encountering an unknown{...}sequence, the code writes a literal'{'tooutbut does not advancep, which will cause an infinite loop on that character; you should incrementpin this branch (and possibly copy the rest of the sequence literally) to avoid getting stuck.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Several editor operations ignore allocation failures from the gap buffer helpers: for example, `e9editor_set_text`, `e9editor_insert`, and `e9editor_load_file` call `buffer_insert()` without checking its `-1` error return, which can leave the editor in a partially updated or inconsistent state; consider propagating errors and failing the operation cleanly when allocation fails.
- In `e9menu_substitute_vars`, the `{p}` (project directory) expansion computes `len` as `1` when no path separator is found, which will copy the first character of the file path instead of using the current directory; you likely intended to fall back to `.` when there is no directory component, so the no-separator case should set `len` to `0` and hit the `.` branch.
- Also in `e9menu_substitute_vars`, when encountering an unknown `{...}` sequence, the code writes a literal `'{'` to `out` but does not advance `p`, which will cause an infinite loop on that character; you should increment `p` in this branch (and possibly copy the rest of the sequence literally) to avoid getting stuck.
## Individual Comments
### Comment 1
<location> `src/e9studio/gui/e9studio_app.c:214-215` </location>
<code_context>
+ ed->buffer->gap_end = ed->buffer->capacity;
+ ed->buffer->size = 0;
+
+ /* Insert new text */
+ buffer_insert(ed->buffer, 0, text, len);
+ ed->dirty = 0;
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Handle allocation failure from buffer_insert in e9editor_set_text.
buffer_insert() returns -1 on allocation failure, but e9editor_set_text() ignores this and still marks the editor as clean. This can leave the buffer empty, the editor not dirty, and OOM errors silent. Please check the return value, propagate an error, and keep the previous buffer contents on failure instead of always returning 0.
</issue_to_address>
### Comment 2
<location> `src/e9studio/gui/e9studio_app.c:279` </location>
<code_context>
+{
+ if (!ed || !ed->buffer || !text || len == 0) return;
+
+ buffer_insert(ed->buffer, pos, text, len);
+ ed->dirty = 1;
+
</code_context>
<issue_to_address>
**issue:** e9editor_insert should propagate buffer_insert allocation failures.
Here, buffer_insert()’s return value is ignored, so an allocation failure would silently drop the edit while still marking the buffer dirty and potentially affecting history. Please check the return code and either avoid updating editor state on failure or propagate an error so the caller can handle it.
</issue_to_address>
### Comment 3
<location> `src/e9studio/gui/e9studio_app.c:813-818` </location>
<code_context>
+ }
+ }
+ p += 2;
+ } else if (*p == 'p' && p[1] == '}') {
+ /* {p} = project directory (same as file dir) */
+ if (file_path) {
+ const char *last = strrchr(file_path, '/');
+ if (!last) last = strrchr(file_path, '\\');
+ size_t len = last ? (size_t)(last - file_path) : 1;
+ if (out_pos + len < out_size - 1) {
+ if (len > 0) {
</code_context>
<issue_to_address>
**issue:** Project directory substitution {p} does not behave correctly for paths without a separator.
For paths without '/' or '\\', `last` stays NULL and `len` becomes 1, so only the first character of `file_path` is copied (e.g., `"file.c"` → `"f"`). The `else` branch that writes `'.'` is never used because `len` is never 0. Please adjust this logic so separator-less paths produce a sensible project directory (e.g., `"./"` or the full path) instead of a single truncated character.
</issue_to_address>
### Comment 4
<location> `src/e9studio/gui/platform/e9gui_cli.c:205-206` </location>
<code_context>
+ return;
+ }
+
+ /* Try as hex address first */
+ uint64_t addr = strtoull(arg, NULL, 16);
+ if (addr != 0) {
+ /* TODO: Disassemble at address */
</code_context>
<issue_to_address>
**suggestion (bug_risk):** cmd_disasm cannot disassemble address 0x0 and conflates invalid addresses with zero.
Because `strtoull()` is called with a `NULL` end pointer, any result of 0 is treated as "not an address" and the code falls back to treating the argument as a function name. This both forbids disassembly at address 0x0 and makes invalid strings like "zz" indistinguishable from 0x0. Use `strtoull(arg, &endptr, 16)` and verify that `endptr > arg` (at least one hex digit consumed) so you can accept address 0 while rejecting invalid numeric input.
</issue_to_address>
### Comment 5
<location> `src/e9studio/gui/e9studio_app.c:889-893` </location>
<code_context>
+ return 0;
+}
+
+int e9build_run_command(const char *cmd)
+{
+ if (!cmd) return -1;
+ return system(cmd);
+}
+
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Consider constraining or documenting the security implications of system() in e9build_run_command.
e9build_run_command() forwards cmd directly to system(), so any substituted paths or config/menu-derived values become part of a shell command. If those inputs are ever user- or externally-controlled, this is a command injection risk. Either clearly document that only trusted input is allowed, or sanitize/switch to a non-shell execution model (e.g., exec with an argv array).
```suggestion
/*
* Executes a build command via the system shell.
*
* SECURITY NOTE:
* `cmd` MUST be constructed only from trusted data (no user- or externally-
* controlled input) because it is passed directly to system(3). Callers are
* responsible for ensuring that any substituted paths, configuration values,
* or menu-derived strings cannot be used for command injection.
*/
int e9build_run_command(const char *cmd)
{
if (!cmd) return -1;
return system(cmd);
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| /* Insert new text */ | ||
| buffer_insert(ed->buffer, 0, text, len); |
There was a problem hiding this comment.
issue (bug_risk): Handle allocation failure from buffer_insert in e9editor_set_text.
buffer_insert() returns -1 on allocation failure, but e9editor_set_text() ignores this and still marks the editor as clean. This can leave the buffer empty, the editor not dirty, and OOM errors silent. Please check the return value, propagate an error, and keep the previous buffer contents on failure instead of always returning 0.
| { | ||
| if (!ed || !ed->buffer || !text || len == 0) return; | ||
|
|
||
| buffer_insert(ed->buffer, pos, text, len); |
There was a problem hiding this comment.
issue: e9editor_insert should propagate buffer_insert allocation failures.
Here, buffer_insert()’s return value is ignored, so an allocation failure would silently drop the edit while still marking the buffer dirty and potentially affecting history. Please check the return code and either avoid updating editor state on failure or propagate an error so the caller can handle it.
| } else if (*p == 'p' && p[1] == '}') { | ||
| /* {p} = project directory (same as file dir) */ | ||
| if (file_path) { | ||
| const char *last = strrchr(file_path, '/'); | ||
| if (!last) last = strrchr(file_path, '\\'); | ||
| size_t len = last ? (size_t)(last - file_path) : 1; |
There was a problem hiding this comment.
issue: Project directory substitution {p} does not behave correctly for paths without a separator.
For paths without '/' or '\', last stays NULL and len becomes 1, so only the first character of file_path is copied (e.g., "file.c" → "f"). The else branch that writes '.' is never used because len is never 0. Please adjust this logic so separator-less paths produce a sensible project directory (e.g., "./" or the full path) instead of a single truncated character.
| /* Try as hex address first */ | ||
| uint64_t addr = strtoull(arg, NULL, 16); |
There was a problem hiding this comment.
suggestion (bug_risk): cmd_disasm cannot disassemble address 0x0 and conflates invalid addresses with zero.
Because strtoull() is called with a NULL end pointer, any result of 0 is treated as "not an address" and the code falls back to treating the argument as a function name. This both forbids disassembly at address 0x0 and makes invalid strings like "zz" indistinguishable from 0x0. Use strtoull(arg, &endptr, 16) and verify that endptr > arg (at least one hex digit consumed) so you can accept address 0 while rejecting invalid numeric input.
| int e9build_run_command(const char *cmd) | ||
| { | ||
| if (!cmd) return -1; | ||
| return system(cmd); | ||
| } |
There was a problem hiding this comment.
🚨 suggestion (security): Consider constraining or documenting the security implications of system() in e9build_run_command.
e9build_run_command() forwards cmd directly to system(), so any substituted paths or config/menu-derived values become part of a shell command. If those inputs are ever user- or externally-controlled, this is a command injection risk. Either clearly document that only trusted input is allowed, or sanitize/switch to a non-shell execution model (e.g., exec with an argv array).
| int e9build_run_command(const char *cmd) | |
| { | |
| if (!cmd) return -1; | |
| return system(cmd); | |
| } | |
| /* | |
| * Executes a build command via the system shell. | |
| * | |
| * SECURITY NOTE: | |
| * `cmd` MUST be constructed only from trusted data (no user- or externally- | |
| * controlled input) because it is passed directly to system(3). Callers are | |
| * responsible for ensuring that any substituted paths, configuration values, | |
| * or menu-derived strings cannot be used for command injection. | |
| */ | |
| int e9build_run_command(const char *cmd) | |
| { | |
| if (!cmd) return -1; | |
| return system(cmd); | |
| } |
There was a problem hiding this comment.
Pull request overview
This PR introduces a GUI framework for E9Studio (v0.1.0) inspired by tedit-cosmo's extensibility philosophy. The implementation adds a command-line interface with platform abstraction, supporting CLI mode as the primary interface with TUI fallback when GUI backends are unavailable.
Changes:
- Added GUI framework with platform abstraction layer supporting Windows, Linux, macOS, and BSD
- Implemented CLI mode with binary analysis commands (disassembly, hex dump, symbols, etc.)
- Added WASM payload build system with Binaryen integration for extensibility
- Integrated text editor functionality with gap buffer implementation and language detection
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Makefile.e9studio | Added GUI and WASM build targets, binaryen integration, and updated help text |
| src/e9studio/wasm/Makefile.wasm | New WASM payload build system for e9studio with Binaryen optimization support |
| src/e9studio/gui/e9studio_gui.h | GUI framework API with platform detection, panel system, and plugin architecture |
| src/e9studio/gui/e9studio_gui.c | GUI framework implementation with stub backends and TUI fallback |
| src/e9studio/gui/e9studio_tedit.h | Editor integration layer between tedit-cosmo and e9studio analysis engine |
| src/e9studio/gui/e9studio_app.c | Application core with editor management, build system, and analysis integration |
| src/e9studio/gui/e9studio_gui_main.c | Main entry point with mode selection (TUI/GUI/CLI) and self-test functionality |
| src/e9studio/gui/platform/e9gui_cli.c | CLI platform backend with command handling and stub platform implementations |
| src/e9studio/gui/config/menuini.txt | INI-based menu configuration for extensibility |
| src/e9studio/gui/TEDITOR_DESIGN.md | Architecture documentation describing design philosophy and implementation phases |
| releases/e9studio-gui.com | Compiled binary release (ELF executable) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| uint64_t addr = 0; | ||
| size_t size = 256; | ||
|
|
There was a problem hiding this comment.
Missing input validation for the 'arg' parameter. The sscanf call attempts to parse 'args' but doesn't check if 'args' is NULL or empty before parsing. If the function is called with NULL or an invalid pointer, this will lead to undefined behavior. Add a check to validate the input parameter before using it.
| if (!args || !args[0]) { | |
| printf("Usage: hex <address> [size]\n"); | |
| return; | |
| } |
| ed->buffer->size = 0; | ||
|
|
||
| /* Insert new text */ | ||
| buffer_insert(ed->buffer, 0, text, len); |
There was a problem hiding this comment.
The buffer_insert function can fail due to allocation failure, but the return value is not checked at the call sites. This could lead to silent data loss when insertions fail. The call in e9editor_set_text at line 215 and e9editor_insert at line 279 should check the return value and handle allocation failures appropriately.
| buffer_insert(ed->buffer, 0, text, len); | |
| if (buffer_insert(ed->buffer, 0, text, len) != 0) { | |
| /* Insertion failed (likely due to allocation failure) */ | |
| return -1; | |
| } |
| { | ||
| if (!ed || !ed->buffer || !text || len == 0) return; | ||
|
|
||
| buffer_insert(ed->buffer, pos, text, len); |
There was a problem hiding this comment.
The buffer_insert function can fail due to allocation failure, but the return value is not checked. This could lead to silent data loss when insertions fail. Check the return value and handle allocation failures appropriately, either by propagating the error or notifying the user.
| buffer_insert(ed->buffer, pos, text, len); | |
| int rc = buffer_insert(ed->buffer, pos, text, len); | |
| if (rc != 0) { | |
| fprintf(stderr, "e9editor_insert: buffer_insert failed (code %d)\n", rc); | |
| return; | |
| } |
| uint64_t addr = strtoull(arg, NULL, 16); | ||
| if (addr != 0) { | ||
| /* TODO: Disassemble at address */ | ||
| printf("Disassembly at 0x%llx:\n", (unsigned long long)addr); | ||
| e9analysis_disasm_at(g_app->binary, addr, 32); | ||
| } else { |
There was a problem hiding this comment.
The sscanf call does not validate the parsed address value properly. When strtoull returns 0, it could indicate either a successful parse of "0" or a parsing error. The code at lines 206-211 treats any non-zero value as a valid address and zero as an invalid address, but this logic is incorrect since address 0x0 is a valid address. Consider checking errno or the endptr parameter from strtoull to properly distinguish between parsing errors and valid zero addresses.
| @@ -0,0 +1,279 @@ | |||
| # E9Studio GUI (cosmo-teditor) | |||
There was a problem hiding this comment.
Typo in comment: "cosmo-teditor" should be "tedit-cosmo" to match the terminology used elsewhere in the codebase (as seen in line 5 and throughout other files).
| # E9Studio GUI (cosmo-teditor) | |
| # E9Studio GUI (tedit-cosmo) |
- Handle buffer_insert allocation failures in e9editor_set_text/insert
- Change e9editor_insert signature from void to int for error propagation
- Fix {p} project directory substitution for paths without separator
- Fix cmd_disasm to properly handle address 0x0 vs parse errors
- Add NULL validation in cmd_hex before sscanf
- Validate empty command strings in e9build_run_command
- Add security note for e9build_run_command about trusted input
- Fix typo: cosmo-teditor -> tedit-cosmo in TEDITOR_DESIGN.md
APE (Actually Portable Executable) patching: - e9ape.c/h: Parse and patch polyglot APE binaries - Handles MZ/PE + ELF + shell script + ZipOS layers - Preserves polyglot validity across all formats WASM/Binaryen integration: - e9binaryen.c/h: Binaryen-based WASM optimization - Integration with ludoplex-binaryen fork Analysis enhancements: - e9analysis.c/h: Extended analysis for APE format detection - e9patch.h: APE-aware patching API BDD specs: - ape_detection.feature: APE format detection scenarios - ape_patching.feature: Polyglot patching scenarios - zipos_access.feature: ZipOS filesystem scenarios Schema specs: - e9ape.schema: APE data structures - e9ape.sm: APE patching state machine Ring: 0 (pure C, cosmocc compatible) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RE analysis of hello.ape (cosmocc output) revealed: - APE has NO x86-64 ELF header embedded - Shell script bootstrap handles Linux execution - PE sections are the ground truth for code layout - ARM64 ELF exists at 0x3C000 (not x86-64) - file_offset often equals RVA in APE Changes: - e9ape.schema: Updated with correct APE structure from RE - e9ape.c: Rewrote to use PE sections for patching - e9_ape_rva_to_offset(): Primary address translation - e9_ape_patch_offset(): Direct file offset patching - e9_ape_patch_rva(): RVA-based patching - e9_ape_patch(): Legacy VA compat (maps to PE) - e9ape.h: Updated API to reflect PE-centric approach Analysis method: - Built hello.ape with cosmocc - Examined with od, readelf, objdump - Parsed PE/ZIP structures with python - Documented findings in schema comments Ring: 0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…patching Add live reload integration layer that connects: - File watcher (inotify) to detect C source changes - cosmocc recompilation for incremental builds - Binaryen object diffing to generate patches - APE patching via PE-based APIs (no x86-64 ELF assumption!) - Instruction cache flush for safe code replacement New files: - specs/e9livereload.schema: Protocol spec (cosmicringforge style) - src/e9patch/e9livereload.h: Public API for live reload - src/e9patch/e9livereload.c: Integration implementation Updated: - e9studio.c: Use e9livereload for file watching and hot-patching - Makefile.e9studio: Add APE and livereload sources to build - AGENTS.md: Document live reload workflow This enables viewing C source code changes in real-time by updating the running APE binary in place, leveraging cosmicringforge spec-driven workflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add spec-driven artifacts generated by cosmicringforge workflow:
- gen/domain/e9livereload_types.{h,c}: C types with init/validate
- Generated by schemagen 2.0.0 from specs/e9livereload.schema
- Provides E9LiveReloadConfig, E9PatchState, E9PendingPatch, etc.
- specs/features/e9livereload.feature: BDD test scenarios
- 21 Gherkin scenarios covering full live reload workflow
- File watching, compilation, diffing, patching, icache flush
- Self-patching, statistics, error handling
This follows the cosmicringforge workflow:
Edit spec → make regen → make verify → commit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove reference to shell_start (not in public E9_APEInfo struct) - Use E9_ZipOSEntry from header instead of internal ZipOSEntry typedef - Fix function signatures to match header declarations These errors were correctly caught by CI - tests working as designed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Required for va_list, va_start, va_end used in set_error(). CI correctly caught this linker error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test files for demonstrating the live reload workflow: - target.c: Simple program to patch - test_livereload.c: Full live reload driver with WAMR - test_simple.c: Minimal test without WAMR dependency - Makefile: Build both native and APE versions Workflow: File Watch -> cosmocc Recompile -> Binaryen Diff -> APE Patch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses cosmicringforge generated types from livereload.schema. Implements full hot-patching workflow: - File watch via inotify - Recompilation via cosmocc - Function byte extraction via objdump - Memory patching via ptrace - ICache flush for x86-64 and aarch64 Usage: sudo ./livereload <PID> [source_file] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Platform backends: - Linux (native): inotify for efficient event-driven watching - macOS/BSD (native): kqueue for event-driven watching - APE/Windows/other: stat-based polling (universal fallback) For APE builds: - Uses _COSMO_SOURCE for ptrace() and IsLinux()/IsBsd() runtime detection - Polling fallback for file watching (most portable) - Works on all platforms APE supports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove inotify/kqueue backends. stat() polling works everywhere and 100ms latency is imperceptible for a dev tool where recompilation takes way longer anyway. -216 lines of platform-specific code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace ptrace with portable backends: - Linux: process_vm_readv/writev (no stop required!) - Windows: ReadProcessMemory/WriteProcessMemory - Self-patching: mprotect + direct memory access Benefits: - No root needed for same-user processes on Linux - Target process doesn't stop during patching - Works on all platforms APE supports Generated types from: specs/domain/procmem.schema Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- e9procmem.h now uses X-macro pattern for constants: - MEMACCESS_XMACRO for read/write/execute flags - PROCMEM_OS_XMACRO for OS detection - PROCMEM_ARCH_XMACRO for architecture - PROCMEM_STATUS_XMACRO for status codes - Added procmem_*_str() functions via X-macro expansion - livereload.c fixes: - Mark get_function_info as __attribute__((unused)) (WAMR pending) - Increase buffer sizes to fix truncation warnings Ring 0 composability: specs → X-macros → constants + strings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- docs/ARCHITECTURE.md: Component architecture and data flow diagrams - docs/IR_PATCHING.md: IR-based patching strategies for lower latency - TinyCC integration (Ring 0, ~30-50ms vs current ~200-500ms) - Binaryen IR diffing approach - Incremental AST compilation using Lemon/lexgen - specs/behavior/livereload.sm: Live reload session state machine - specs/behavior/patch.sm: Individual patch lifecycle state machine - AGENTS.md: Updated with architecture docs and state machine refs These documents serve as LLM reference for AI assistants working on the codebase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete spec-driven IR patching using CosmicRingForge generators: SPECS (Ring 0 SSOT): - specs/domain/c11_ast.schema: AST node types (40+ node kinds) - FunctionDef, TypeSpec, Statements, Expressions - ASTDiff, FuncChange for diff results - C version abstraction (C89-C23, base: C11/Cosmopolitan) - specs/parsing/c11_tokens.def: Token X-macros - C11_KEYWORDS, C11_OPERATORS, C11_PUNCTUATION - C11_TOKEN_KINDS for categorization - Expandable via defgen to enum + string tables - specs/parsing/c11.lex: Lexer specification - Full C11 lexer rules for lexgen - Multi-state: INITIAL, IN_COMMENT, IN_STRING, IN_CHAR - Escape sequence handling - specs/parsing/c11.grammar: Lemon LALR(1) grammar - Function-focused subset of C11 - AST construction actions - Content hashing for diff - specs/behavior/ast_diff.sm: Diff state machine - INIT -> PARSE_OLD -> PARSE_NEW -> HASH_FUNCS -> COMPARE -> DONE - Detects ADDED, MODIFIED, REMOVED functions - specs/behavior/ir_compile.sm: Selective compile state machine - Only compiles changed functions - Generates stubs for unchanged - Optional TinyCC fast path DOCS: - docs/IR_PATCHING.md: Full Ring 0/1/2 composability documentation - 4-8x latency reduction (~30-80ms vs ~200-500ms) - C version abstraction strategy - Data flow diagrams - Build integration All specs use CosmicRingForge generators (schemagen, defgen, lexgen, lemon, smgen) - fully Ring 0 dogfooded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add MANDATORY upstream reading section to AGENTS.md - Add tool compatibility table (TinyCC banned, Binaryen OK via ludoplex) - Update IR_PATCHING.md with ludoplex/binaryen as Ring 1 - Remove TinyCC from latency comparison (incompatible with Cosmopolitan) - Update file map to reflect actual directory structure Contains errors: narrowed focus to Ring 0, needs Ring 1/2 coverage fixes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TinyCC produces "Invalid relocation entry" errors when linking with cosmopolitan.a. This is a fundamental incompatibility. Changes: - ir_compile.sm: Remove USE_TCC option, replace with ccache guard - ape-anatomy-analysis.md: Replace TinyCC with binaryen.wasm, add warning - ARCHITECTURE.md: Update options list with Ring 0 AST as recommended - build-e9studio.yml: Replace tcc.wasm with binaryen.wasm Use Ring 0 AST-based parsing (Lemon + lexgen) or cosmocc for compilation. Use ludoplex/binaryen for WASM IR diffing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parent repo renamed from ludoplex/cosmicringforge to ludoplex/cosmo-bde Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add e9studio-gui.com v0.1.0 release binary
@claude
claude committed 16 hours ago
Summary by Sourcery
Introduce the initial E9Studio GUI/CLI framework and core application layer for text editing, binary analysis integration, and platform abstraction.
New Features:
Enhancements: