mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-08-11 16:18:07 +00:00
capture output on linux
This commit is contained in:
committed by
Ryan Fleury
parent
05fc085dc6
commit
60a38247af
+155
-26
@@ -295,7 +295,6 @@ t_cwd_path(void)
|
|||||||
Temp scratch = scratch_begin(0, 0);
|
Temp scratch = scratch_begin(0, 0);
|
||||||
String8 cwd = get_current_path(scratch.arena);
|
String8 cwd = get_current_path(scratch.arena);
|
||||||
cwd = str8_chop_last_slash(cwd);
|
cwd = str8_chop_last_slash(cwd);
|
||||||
cwd = str8f(scratch.arena, "%S", cwd);
|
|
||||||
MemoryCopyStr8(path, cwd);
|
MemoryCopyStr8(path, cwd);
|
||||||
path[cwd.size] = 0;
|
path[cwd.size] = 0;
|
||||||
scratch_end(scratch);
|
scratch_end(scratch);
|
||||||
@@ -325,7 +324,7 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
|
|||||||
B32 is_ok = 0;
|
B32 is_ok = 0;
|
||||||
|
|
||||||
// clean up global state
|
// clean up global state
|
||||||
arena_pop_to(g_output_arena, 0);
|
arena_clear(g_output_arena);
|
||||||
MemoryZeroStruct(&g_output);
|
MemoryZeroStruct(&g_output);
|
||||||
g_last_exit_code = max_U64;
|
g_last_exit_code = max_U64;
|
||||||
|
|
||||||
@@ -383,12 +382,36 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
|
|||||||
for EachElement(i, captures_win32) { read_capture_handles[i] = (File){ (U64)captures_win32[i].read_pipe_handle }; }
|
for EachElement(i, captures_win32) { read_capture_handles[i] = (File){ (U64)captures_win32[i].read_pipe_handle }; }
|
||||||
for EachElement(i, captures_win32) { write_capture_handles[i] = (File){ (U64)captures_win32[i].write_pipe_handle }; }
|
for EachElement(i, captures_win32) { write_capture_handles[i] = (File){ (U64)captures_win32[i].write_pipe_handle }; }
|
||||||
#else
|
#else
|
||||||
# error NotImplemented
|
typedef struct {
|
||||||
|
int fds[2];
|
||||||
|
String8List *parts;
|
||||||
|
B32 is_live;
|
||||||
|
struct pollfd *poll_fd;
|
||||||
|
} LinuxCapture;
|
||||||
|
|
||||||
|
LinuxCapture captures_linux[2] = {0};
|
||||||
|
for EachElement(i, captures_linux) {
|
||||||
|
if (pipe2(captures_linux[i].fds, 0) != 0) {
|
||||||
|
fprintf(stderr, "ERROR: failed to create pipe for output capture\n");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
captures_linux[i].is_live = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
captures_linux[0].parts = &stdout_parts;
|
||||||
|
captures_linux[1].parts = &stderr_parts;
|
||||||
|
|
||||||
|
File read_capture_handles[2] = {0}, write_capture_handles[2] = {0};
|
||||||
|
read_capture_handles[0].u64[0] = captures_linux[0].fds[0];
|
||||||
|
read_capture_handles[1].u64[0] = captures_linux[1].fds[0];
|
||||||
|
write_capture_handles[0].u64[0] = captures_linux[0].fds[1];
|
||||||
|
write_capture_handles[1].u64[0] = captures_linux[1].fds[1];
|
||||||
#endif
|
#endif
|
||||||
// Build Launch Options
|
// Build Launch Options
|
||||||
ProcessLaunchParams launch_opts = {
|
ProcessLaunchParams launch_opts = {
|
||||||
.path = g_wdir,
|
.path = g_wdir,
|
||||||
.inherit_env = 1,
|
.inherit_env = 1,
|
||||||
|
.consoleless = 1,
|
||||||
.env = env,
|
.env = env,
|
||||||
.stdout_file = write_capture_handles[stdout_idx],
|
.stdout_file = write_capture_handles[stdout_idx],
|
||||||
.stderr_file = write_capture_handles[stderr_idx],
|
.stderr_file = write_capture_handles[stderr_idx],
|
||||||
@@ -425,41 +448,41 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
|
|||||||
wait_handles[wait_handle_count++] = (HANDLE)process_handle.u64[0];
|
wait_handles[wait_handle_count++] = (HANDLE)process_handle.u64[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
for EachElement(i, captures_win32) {
|
for EachElement(capture_idx, captures_win32) {
|
||||||
while (captures_win32[i].state == Win32CaptureState_Null) {
|
while (captures_win32[capture_idx].state == Win32CaptureState_Null) {
|
||||||
// init overlapped so when child writes to capture buffer this event is signaled
|
// init overlapped so when child writes to capture buffer this event is signaled
|
||||||
AssertAlways(ResetEvent(captures_win32[i].event));
|
AssertAlways(ResetEvent(captures_win32[capture_idx].event));
|
||||||
MemoryZeroStruct(&captures_win32[i].overlapped);
|
MemoryZeroStruct(&captures_win32[capture_idx].overlapped);
|
||||||
captures_win32[i].overlapped.hEvent = captures_win32[i].event;
|
captures_win32[capture_idx].overlapped.hEvent = captures_win32[capture_idx].event;
|
||||||
|
|
||||||
// begin overlapped read
|
// begin overlapped read
|
||||||
DWORD read_size = 0;
|
DWORD read_size = 0;
|
||||||
if (ReadFile(captures_win32[i].read_pipe_handle, captures_win32[i].buffer, captures_win32[i].buffer_size, &read_size, &captures_win32[i].overlapped)) {
|
if (ReadFile(captures_win32[capture_idx].read_pipe_handle, captures_win32[capture_idx].buffer, captures_win32[capture_idx].buffer_size, &read_size, &captures_win32[capture_idx].overlapped)) {
|
||||||
if (read_size > 0) {
|
if (read_size > 0) {
|
||||||
String8 string = str8(captures_win32[i].buffer, read_size);
|
String8 string = str8(captures_win32[capture_idx].buffer, read_size);
|
||||||
String8 string_copy = str8_copy(scratch.arena, string);
|
String8 string_copy = str8_copy(scratch.arena, string);
|
||||||
str8_list_push(scratch.arena, captures_win32[i].parts, string_copy);
|
str8_list_push(scratch.arena, captures_win32[capture_idx].parts, string_copy);
|
||||||
} else {
|
} else {
|
||||||
captures_win32[i].state = Win32CaptureState_EOF;
|
captures_win32[capture_idx].state = Win32CaptureState_EOF;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DWORD error = GetLastError();
|
DWORD error = GetLastError();
|
||||||
if (error == ERROR_IO_PENDING) {
|
if (error == ERROR_IO_PENDING) {
|
||||||
captures_win32[i].state = Win32CaptureState_Pending;
|
captures_win32[capture_idx].state = Win32CaptureState_Pending;
|
||||||
} else {
|
} else {
|
||||||
AssertAlways(error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE);
|
AssertAlways(error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE);
|
||||||
captures_win32[i].state = Win32CaptureState_EOF;
|
captures_win32[capture_idx].state = Win32CaptureState_EOF;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (captures_win32[i].state == Win32CaptureState_Pending) {
|
if (captures_win32[capture_idx].state == Win32CaptureState_Pending) {
|
||||||
// event now should signal whenever pipe has data to read
|
// event now should signal whenever pipe has data to read
|
||||||
captures_win32[i].wait_idx = wait_handle_count++;
|
captures_win32[capture_idx].wait_idx = wait_handle_count++;
|
||||||
wait_handles[captures_win32[i].wait_idx] = captures_win32[i].event;
|
wait_handles[captures_win32[capture_idx].wait_idx] = captures_win32[capture_idx].event;
|
||||||
} else {
|
} else {
|
||||||
captures_win32[i].wait_idx = max_U64;
|
captures_win32[capture_idx].wait_idx = max_U64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,14 +492,10 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
|
|||||||
// compute wait time
|
// compute wait time
|
||||||
DWORD wait_ms = INFINITE;
|
DWORD wait_ms = INFINITE;
|
||||||
if (timeout_us != max_U64) {
|
if (timeout_us != max_U64) {
|
||||||
U64 now_us = os_now_microseconds();
|
U64 now_us = now_time_us();
|
||||||
wait_ms = now_us < endt_us ? ClampTop((endt_us - now_us + 999) / 1000, max_U32-1) : 0;
|
wait_ms = now_us < endt_us ? ClampTop((endt_us - now_us + 999) / 1000, max_U32-1) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wait_ms == 0) {
|
|
||||||
DebugBreakProcess((HANDLE)process_handle.u64[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait on process and read pipes
|
// wait on process and read pipes
|
||||||
DWORD wait_result = WaitForMultipleObjects(wait_handle_count, wait_handles, 0, wait_ms);
|
DWORD wait_result = WaitForMultipleObjects(wait_handle_count, wait_handles, 0, wait_ms);
|
||||||
|
|
||||||
@@ -565,7 +584,117 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#elif OS_LINUX
|
#elif OS_LINUX
|
||||||
# error NotImplemented
|
// close handle so read(2) does not block
|
||||||
|
for EachElement(i, write_capture_handles) {
|
||||||
|
close((int)write_capture_handles[i].u64[0]);
|
||||||
|
MemoryZeroStruct(&write_capture_handles[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t pid = (pid_t)process_handle.u64[0];
|
||||||
|
int pidfd = syscall(SYS_pidfd_open, pid, 0);
|
||||||
|
if (pidfd < 0) {
|
||||||
|
fprintf(stderr, "ERROR: failed to translate pid(%d) to pidfd\n", pid);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
B32 is_process_live = 1;
|
||||||
|
U64 endt_us = ENDT_US(timeout_us);
|
||||||
|
U64 read_buffer_default_size = MB(1);
|
||||||
|
U64 read_buffer_size = read_buffer_default_size;
|
||||||
|
U8 *read_buffer = push_array(scratch.arena, U8, read_buffer_default_size);
|
||||||
|
for (;;) {
|
||||||
|
struct pollfd fds[3] = {0};
|
||||||
|
int nfds = 0;
|
||||||
|
|
||||||
|
// append process
|
||||||
|
if (is_process_live) {
|
||||||
|
fds[nfds++] = (struct pollfd){ .fd = pidfd, .events = POLLIN | POLLHUP };
|
||||||
|
}
|
||||||
|
|
||||||
|
// append pipes
|
||||||
|
for EachElement(i, captures_linux) {
|
||||||
|
if (captures_linux[i].is_live) {
|
||||||
|
captures_linux[i].poll_fd = &fds[nfds];
|
||||||
|
fds[nfds++] = (struct pollfd){ .fd = captures_linux[i].fds[0], .events = POLLIN | POLLHUP };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit if there are no more handles to poll
|
||||||
|
if (nfds == 0) { break; }
|
||||||
|
|
||||||
|
// compute wait time
|
||||||
|
int wait_ms = -1;
|
||||||
|
if (timeout_us != max_U64) {
|
||||||
|
U64 now_us = now_time_us();
|
||||||
|
wait_ms = now_us < endt_us ? ClampTop((endt_us - now_us + 999) / 1000, max_U32-1) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for kernel to signal any of the wait handles
|
||||||
|
int poll_result = poll(fds, nfds, wait_ms);
|
||||||
|
if (poll_result < 0) {
|
||||||
|
fprintf(stderr, "ERROR: poll failed with errono %d\n", errno);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (poll_result == 0) {
|
||||||
|
fprintf(stderr, "WARNING: poll timeout\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// handle case where process exited while waiting for a signal
|
||||||
|
if (is_process_live) {
|
||||||
|
if (fds[0].revents & POLLIN) {
|
||||||
|
// reap process
|
||||||
|
int status;
|
||||||
|
if (waitpid(pid, &status, 0) >= 0) {
|
||||||
|
g_last_exit_code = WEXITSTATUS(status);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "ERROR: failed to reap process %d\n", pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// signal on process fd means exit
|
||||||
|
is_process_live = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for EachElement(i, captures_linux) {
|
||||||
|
if (captures_linux[i].is_live) {
|
||||||
|
if (captures_linux[i].poll_fd->revents & POLLIN) {
|
||||||
|
for (;;) {
|
||||||
|
if (read_buffer_size == 0) {
|
||||||
|
read_buffer_size = read_buffer_default_size;
|
||||||
|
read_buffer = push_array(scratch.arena, U8, read_buffer_default_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t read_size = OS_LNX_RETRY_ON_EINTR(read(captures_linux[i].poll_fd->fd, read_buffer, read_buffer_size));
|
||||||
|
if (read_size > 0) {
|
||||||
|
str8_list_push(scratch.arena, captures_linux[i].parts, str8(read_buffer, read_size));
|
||||||
|
read_buffer += read_size;
|
||||||
|
read_buffer_size -= read_size;
|
||||||
|
} else if (read_size == 0) {
|
||||||
|
captures_linux[i].is_live = 0;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
// no more data in the pipe
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "ERROR: failed to read pipe, errno %d\n", errno);
|
||||||
|
captures_linux[i].is_live = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (captures_linux[i].poll_fd->revents & POLLHUP) {
|
||||||
|
captures_linux[i].is_live = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close process handle
|
||||||
|
if (close(pidfd) < 0) {
|
||||||
|
fprintf(stderr, "ERROR: failed to close process handle %d\n", pidfd);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// update output global
|
// update output global
|
||||||
@@ -1062,9 +1191,9 @@ t_entry_point(CmdLine *cmdline)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run test
|
// run test
|
||||||
U64 run_start_time = os_now_microseconds();
|
U64 run_start_time = now_time_us();
|
||||||
T_RunResult result = t_run(g_torture_tests[target_idx].r, g_torture_tests[target_idx].user_data);
|
T_RunResult result = t_run(g_torture_tests[target_idx].r, g_torture_tests[target_idx].user_data);
|
||||||
U64 run_end_time = os_now_microseconds();
|
U64 run_end_time = now_time_us();
|
||||||
// print result
|
// print result
|
||||||
if (result.status == T_RunStatus_Pass) {
|
if (result.status == T_RunStatus_Pass) {
|
||||||
fprintf(stdout, "\x1b[32m" "%s" "\x1b[0m", t_string_from_result(result.status));
|
fprintf(stdout, "\x1b[32m" "%s" "\x1b[0m", t_string_from_result(result.status));
|
||||||
|
|||||||
Reference in New Issue
Block a user