From 363b52f667ca690c4d6965bceabb34da5394e2fb Mon Sep 17 00:00:00 2001 From: nstarke Date: Sat, 21 Mar 2026 22:03:40 -0500 Subject: [PATCH 1/4] fixing small bug --- agent/linux/linux_gdbserver_cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/linux/linux_gdbserver_cmd.c b/agent/linux/linux_gdbserver_cmd.c index af8b0e2..585d791 100644 --- a/agent/linux/linux_gdbserver_cmd.c +++ b/agent/linux/linux_gdbserver_cmd.c @@ -3292,7 +3292,7 @@ static void do_continue(int fd, int initial_sig) static void handle_packet(int fd, char *pkt) { char resp[ELA_GDB_RSP_MAX_FRAMED]; - char hex[ELA_GDB_RSP_MAX_PACKET]; + char hex[ELA_GDB_RSP_MAX_PACKET + 1]; /* +1 for null after max 2048-byte hex payload */ uint64_t addr, len_val; uint8_t data_buf[2048]; char *sep, *colon; From 32347a839015517d9d6b124900e44bffa6275a50 Mon Sep 17 00:00:00 2001 From: nstarke Date: Sat, 21 Mar 2026 22:09:44 -0500 Subject: [PATCH 2/4] MIPS fixes --- agent/linux/linux_gdbserver_cmd.c | 74 +++++++++++++++---------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/agent/linux/linux_gdbserver_cmd.c b/agent/linux/linux_gdbserver_cmd.c index 585d791..dc6ee18 100644 --- a/agent/linux/linux_gdbserver_cmd.c +++ b/agent/linux/linux_gdbserver_cmd.c @@ -900,8 +900,6 @@ static int regs_write(const char *hex, size_t hex_len) #endif /* - * Common MIPS ptrace helper: fetch elf_gregset_t via PTRACE_GETREGSET. - * Used by both the 32-bit and 64-bit variants below. * The kernel-side elf_gregset_t is unsigned long[45]; on MIPS64 each slot * is 8 bytes, on MIPS32 each slot is 4 bytes. * @@ -916,7 +914,31 @@ static int regs_write(const char *hex, size_t hex_len) * f0-f31 (FPRs, GDB regs 38-69, zero-filled for FPUless targets) * fcsr (GDB reg 70, zero) * fir (GDB reg 71, zero) + * + * Older MIPS kernels (pre-3.14) do not implement PTRACE_GETREGSET. + * mips_get_regs/mips_set_regs fall back to PTRACE_GETREGS/PTRACE_SETREGS + * which have been available since the earliest MIPS Linux kernels. + * Both interfaces use the same elf_gregset_t layout. */ +static int mips_get_regs(unsigned long *regs, size_t regs_sz) +{ + struct iovec iov = { regs, regs_sz }; + + if (ptrace(PTRACE_GETREGSET, g_pid, + (void *)(uintptr_t)NT_PRSTATUS, &iov) == 0) + return 0; + return ptrace(PTRACE_GETREGS, g_pid, NULL, regs) != 0 ? -1 : 0; +} + +static int mips_set_regs(unsigned long *regs, size_t regs_sz) +{ + struct iovec iov = { regs, regs_sz }; + + if (ptrace(PTRACE_SETREGSET, g_pid, + (void *)(uintptr_t)NT_PRSTATUS, &iov) == 0) + return 0; + return ptrace(PTRACE_SETREGS, g_pid, NULL, regs) != 0 ? -1 : 0; +} #ifdef __mips64 @@ -926,13 +948,11 @@ static int regs_write(const char *hex, size_t hex_len) static int regs_read(char *out, size_t out_sz) { unsigned long regs[MIPS_ELF_NGREG]; - struct iovec iov = { regs, sizeof(regs) }; char tmp[17]; size_t pos = 0; int i; - if (ptrace(PTRACE_GETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0) + if (mips_get_regs(regs, sizeof(regs)) != 0) return -1; #define EMIT64(v) do { \ @@ -972,10 +992,8 @@ static int regs_read(char *out, size_t out_sz) static int reg_read_one(int regnum, char *out, size_t out_sz) { unsigned long regs[MIPS_ELF_NGREG]; - struct iovec iov = { regs, sizeof(regs) }; - if (ptrace(PTRACE_GETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0) + if (mips_get_regs(regs, sizeof(regs)) != 0) return -1; if (regnum >= 0 && regnum <= 31) @@ -1001,11 +1019,9 @@ static int reg_read_one(int regnum, char *out, size_t out_sz) static int reg_write_one(int regnum, const char *hex_val) { unsigned long regs[MIPS_ELF_NGREG]; - struct iovec iov = { regs, sizeof(regs) }; uint64_t v64; - if (ptrace(PTRACE_GETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0) + if (mips_get_regs(regs, sizeof(regs)) != 0) return -1; if (regnum >= 0 && regnum <= 31) { @@ -1038,24 +1054,20 @@ static int reg_write_one(int regnum, const char *hex_val) } } - iov.iov_len = sizeof(regs); - return ptrace(PTRACE_SETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0 ? -1 : 0; + return mips_set_regs(regs, sizeof(regs)); } /* MIPS64 g-packet: 1152 hex (38 GPRs/ctl × 16 + 32 FP × 16 + 2 ctl × 16) */ static int regs_write(const char *hex, size_t hex_len) { unsigned long regs[MIPS_ELF_NGREG]; - struct iovec iov = { regs, sizeof(regs) }; uint64_t v64; size_t pos = 0; int i; if (hex_len < 1152) return -1; - if (ptrace(PTRACE_GETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0) + if (mips_get_regs(regs, sizeof(regs)) != 0) return -1; for (i = 0; i < 32; i++) { @@ -1071,9 +1083,7 @@ static int regs_write(const char *hex, size_t hex_len) if (mips_dec64(hex+pos,&v64)) return -1; regs[MIPS_EF_EPC] =(unsigned long)v64; /* FP registers not written to kernel */ - iov.iov_len = sizeof(regs); - return ptrace(PTRACE_SETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0 ? -1 : 0; + return mips_set_regs(regs, sizeof(regs)); } #else /* MIPS32 */ @@ -1084,13 +1094,11 @@ static int regs_write(const char *hex, size_t hex_len) static int regs_read(char *out, size_t out_sz) { unsigned long regs[MIPS_ELF_NGREG]; - struct iovec iov = { regs, sizeof(regs) }; char tmp[9]; size_t pos = 0; int i; - if (ptrace(PTRACE_GETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0) + if (mips_get_regs(regs, sizeof(regs)) != 0) return -1; #define EMIT32(v) do { \ @@ -1130,10 +1138,8 @@ static int regs_read(char *out, size_t out_sz) static int reg_read_one(int regnum, char *out, size_t out_sz) { unsigned long regs[MIPS_ELF_NGREG]; - struct iovec iov = { regs, sizeof(regs) }; - if (ptrace(PTRACE_GETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0) + if (mips_get_regs(regs, sizeof(regs)) != 0) return -1; if (regnum >= 0 && regnum <= 31) @@ -1159,11 +1165,9 @@ static int reg_read_one(int regnum, char *out, size_t out_sz) static int reg_write_one(int regnum, const char *hex_val) { unsigned long regs[MIPS_ELF_NGREG]; - struct iovec iov = { regs, sizeof(regs) }; uint32_t v32; - if (ptrace(PTRACE_GETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0) + if (mips_get_regs(regs, sizeof(regs)) != 0) return -1; if (regnum >= 0 && regnum <= 31) { @@ -1196,24 +1200,20 @@ static int reg_write_one(int regnum, const char *hex_val) } } - iov.iov_len = sizeof(regs); - return ptrace(PTRACE_SETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0 ? -1 : 0; + return mips_set_regs(regs, sizeof(regs)); } /* MIPS32 g-packet: 576 hex (38 GPRs/ctl × 8 + 32 FP × 8 + 2 ctl × 8) */ static int regs_write(const char *hex, size_t hex_len) { unsigned long regs[MIPS_ELF_NGREG]; - struct iovec iov = { regs, sizeof(regs) }; uint32_t v32; size_t pos = 0; int i; if (hex_len < 576) return -1; - if (ptrace(PTRACE_GETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0) + if (mips_get_regs(regs, sizeof(regs)) != 0) return -1; for (i = 0; i < 32; i++) { @@ -1229,9 +1229,7 @@ static int regs_write(const char *hex, size_t hex_len) if (mips_dec32(hex+pos,&v32)) return -1; regs[MIPS_EF_EPC] =(unsigned long)v32; /* FP registers not written to kernel */ - iov.iov_len = sizeof(regs); - return ptrace(PTRACE_SETREGSET, g_pid, - (void *)(uintptr_t)NT_PRSTATUS, &iov) != 0 ? -1 : 0; + return mips_set_regs(regs, sizeof(regs)); } #endif /* __mips64 */ From 04da206135e1cc4d388a4eb642ada4e595a627b8 Mon Sep 17 00:00:00 2001 From: nstarke Date: Sat, 21 Mar 2026 22:18:51 -0500 Subject: [PATCH 3/4] Improving testing for gdbserver --- Makefile | 5 + agent/linux/linux_gdbserver_cmd.c | 136 +------ agent/linux/linux_gdbserver_pkt_util.c | 109 ++++++ agent/linux/linux_gdbserver_pkt_util.h | 42 ++ tests/unit/agent/main.c | 2 + .../agent/test_linux_gdbserver_pkt_util.c | 359 ++++++++++++++++++ 6 files changed, 526 insertions(+), 127 deletions(-) create mode 100644 agent/linux/linux_gdbserver_pkt_util.c create mode 100644 agent/linux/linux_gdbserver_pkt_util.h create mode 100644 tests/unit/agent/test_linux_gdbserver_pkt_util.c diff --git a/Makefile b/Makefile index 58221ee..897850b 100644 --- a/Makefile +++ b/Makefile @@ -482,6 +482,7 @@ AGENT_UNIT_TEST_SRC := \ tests/unit/agent/test_linux_list_symlinks_util.c \ tests/unit/agent/test_linux_process_watch_util.c \ tests/unit/agent/test_linux_gdbserver_util.c \ + tests/unit/agent/test_linux_gdbserver_pkt_util.c \ tests/unit/agent/test_device_scan.c \ tests/unit/agent/test_dispatch_util.c \ tests/unit/agent/test_dispatch_parse_util.c \ @@ -559,6 +560,7 @@ AGENT_UNIT_TEST_DEPS := \ agent/linux/linux_execute_command_util.c \ agent/linux/linux_process_watch_util.c \ agent/linux/linux_gdbserver_util.c \ + agent/linux/linux_gdbserver_pkt_util.c \ agent/device/device_scan.c \ agent/shell/script_exec_util.c \ agent/shell/interactive_util.c \ @@ -597,6 +599,7 @@ AGENT_UNIT_TEST_DEPS := \ agent/linux/linux_execute_command_util.h \ agent/linux/linux_process_watch_util.h \ agent/linux/linux_gdbserver_util.h \ + agent/linux/linux_gdbserver_pkt_util.h \ agent/net/ela_conf_util.h \ agent/net/ela_conf.h \ agent/net/ws_url_util.h \ @@ -795,6 +798,7 @@ SRC := \ agent/linux/linux_process_watch_util.c \ agent/linux/linux_process_watch_cmd.c \ agent/linux/linux_gdbserver_util.c \ + agent/linux/linux_gdbserver_pkt_util.c \ agent/linux/linux_gdbserver_cmd.c \ agent/tpm2/tpm2_cmd.c \ agent/tpm2/tpm2_util.c \ @@ -1210,6 +1214,7 @@ $(AGENT_UNIT_TEST_BIN): $(AGENT_UNIT_TEST_SRC) $(AGENT_UNIT_TEST_DEPS) $(JSONC_L agent/linux/linux_execute_command_util.c \ agent/linux/linux_process_watch_util.c \ agent/linux/linux_gdbserver_util.c \ + agent/linux/linux_gdbserver_pkt_util.c \ agent/device/device_scan.c \ agent/shell/script_exec_util.c \ agent/shell/interactive_util.c \ diff --git a/agent/linux/linux_gdbserver_cmd.c b/agent/linux/linux_gdbserver_cmd.c index dc6ee18..152a06e 100644 --- a/agent/linux/linux_gdbserver_cmd.c +++ b/agent/linux/linux_gdbserver_cmd.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later - Copyright (c) 2026 Nicholas Starke #include "linux_gdbserver_util.h" +#include "linux_gdbserver_pkt_util.h" #include "../embedded_linux_audit_cmd.h" #include @@ -327,42 +328,6 @@ static int rsp_recv_packet(int fd, char *payload, size_t payload_sz) return ela_gdb_rsp_unframe(raw, pos, payload, payload_sz); } -/* ----------------------------------------------------------------------- - * Thread-id parser (handles both "p." and plain "") - * ---------------------------------------------------------------------- */ - -/* - * Parse an RSP thread-id from the string at s. Stores the parsed TID in - * *out_tid and (if the multiprocess "p." form is present) the PID - * in *out_pid; otherwise *out_pid is set to 0. Returns the number of input - * characters consumed, or -1 on parse error. Either output pointer may be - * NULL if the caller doesn't need that value. - */ -static int parse_thread_id(const char *s, pid_t *out_pid, pid_t *out_tid) -{ - unsigned long long p, t; - int n = 0; - - if (!s || !*s) - return -1; - - if (s[0] == 'p') { - if (sscanf(s + 1, "%llx.%llx%n", &p, &t, &n) == 2) { - if (out_pid) *out_pid = (pid_t)p; - if (out_tid) *out_tid = (pid_t)t; - return 1 + n; - } - return -1; - } - - if (sscanf(s, "%llx%n", &t, &n) == 1) { - if (out_pid) *out_pid = 0; - if (out_tid) *out_tid = (pid_t)t; - return n; - } - return -1; -} - /* ----------------------------------------------------------------------- * Stop reply * ---------------------------------------------------------------------- */ @@ -2689,40 +2654,6 @@ static int build_libraries_svr4_xml(pid_t pid, char *out, size_t out_sz) return -1; } -/* ----------------------------------------------------------------------- - * RSP binary unescape - * ---------------------------------------------------------------------- */ - -/* - * Decode RSP binary encoding from `src` into `dst`. - * The only encoding rule: 0x7d ('}') is an escape byte; the byte that - * follows is XOR'd with 0x20 to recover the real value. - * Writes exactly `expected` decoded bytes; `max_src` is a hard limit on - * how many source bytes may be consumed (prevents reading past the payload - * buffer when the caller does not have its length). - * Returns `expected` on success, -1 if the source is exhausted first. - */ -static int rsp_binary_unescape(const char *src, size_t max_src, - uint8_t *dst, size_t expected) -{ - size_t in = 0, out = 0; - - while (out < expected) { - if (in >= max_src) - return -1; - if ((unsigned char)src[in] == 0x7du) { - in++; - if (in >= max_src) - return -1; - dst[out++] = (uint8_t)((unsigned char)src[in] ^ 0x20u); - } else { - dst[out++] = (uint8_t)(unsigned char)src[in]; - } - in++; - } - return (int)out; -} - /* ----------------------------------------------------------------------- * Memory search helper (qSearch:memory) * ---------------------------------------------------------------------- */ @@ -3073,55 +3004,6 @@ static int wp_remove_x86(uint64_t addr) * vFile helpers * ---------------------------------------------------------------------- */ -/* Big-endian encode helpers for the GDB portable stat structure. */ -static void vfile_put_be32(uint8_t *p, uint32_t v) -{ - p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16); - p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)(v); -} - -static void vfile_put_be64(uint8_t *p, uint64_t v) -{ - vfile_put_be32(p, (uint32_t)(v >> 32)); - vfile_put_be32(p + 4, (uint32_t)(v)); -} - -/* - * Encode a host struct stat into the 64-byte GDB fio_stat structure. - * Fields are big-endian; layout: 7×uint32 + 3×uint64 + 3×uint32. - */ -static void vfile_encode_stat(uint8_t *buf, const struct stat *st) -{ - memset(buf, 0, 64); - vfile_put_be32(buf + 0, (uint32_t)st->st_dev); - vfile_put_be32(buf + 4, (uint32_t)st->st_ino); - vfile_put_be32(buf + 8, (uint32_t)st->st_mode); - vfile_put_be32(buf + 12, (uint32_t)st->st_nlink); - vfile_put_be32(buf + 16, (uint32_t)st->st_uid); - vfile_put_be32(buf + 20, (uint32_t)st->st_gid); - vfile_put_be32(buf + 24, (uint32_t)st->st_rdev); - vfile_put_be64(buf + 28, (uint64_t)st->st_size); - vfile_put_be64(buf + 36, (uint64_t)st->st_blksize); - vfile_put_be64(buf + 44, (uint64_t)st->st_blocks); - vfile_put_be32(buf + 52, (uint32_t)st->st_atime); - vfile_put_be32(buf + 56, (uint32_t)st->st_mtime); - vfile_put_be32(buf + 60, (uint32_t)st->st_ctime); -} - -/* - * Translate GDB fileio open flags to Linux open flags. - * GDB uses its own constants (from gdb/fileio.h); Linux values differ. - */ -static int vfile_gdb_flags_to_linux(int gflags) -{ - int lflags = gflags & 3; /* O_RDONLY/O_WRONLY/O_RDWR values match */ - if (gflags & 0x008) lflags |= O_APPEND; - if (gflags & 0x200) lflags |= O_CREAT; - if (gflags & 0x400) lflags |= O_TRUNC; - if (gflags & 0x800) lflags |= O_EXCL; - return lflags; -} - /* Send a vFile F-response with only a return code (no binary attachment). */ static void vfile_send_rc(int conn_fd, int retcode, int err) { @@ -3400,7 +3282,7 @@ static void handle_packet(int fd, char *pkt) rsp_send_str(fd, "OK"); break; } - n = rsp_binary_unescape(colon + 1, 2 * (size_t)len_val, + n = ela_gdb_rsp_binary_unescape(colon + 1, 2 * (size_t)len_val, data_buf, (size_t)len_val); if (n < 0 || (size_t)n != len_val) { rsp_send_str(fd, "E03"); break; @@ -3496,7 +3378,7 @@ static void handle_packet(int fd, char *pkt) */ /* coverity[resource_leak] */ vfd = open(name_buf, - vfile_gdb_flags_to_linux(gflags), + ela_gdb_vfile_flags_to_linux(gflags), (mode_t)fmode); vfile_send_rc(fd, vfd < 0 ? -1 : vfd, vfd < 0 ? errno : 0); @@ -3557,7 +3439,7 @@ static void handle_packet(int fd, char *pkt) offset = (off_t)strtoull(cm2 + 1, NULL, 16); if (count > sizeof(data_buf)) count = sizeof(data_buf); - decoded = rsp_binary_unescape(semi + 1, count * 2 + 1, + decoded = ela_gdb_rsp_binary_unescape(semi + 1, count * 2 + 1, data_buf, count); if (decoded < 0) { vfile_send_rc(fd, -1, EINVAL); break; } errno = 0; @@ -3575,7 +3457,7 @@ static void handle_packet(int fd, char *pkt) vfile_send_rc(fd, -1, errno); break; } - vfile_encode_stat(stat_buf, &st); + ela_gdb_vfile_encode_stat(stat_buf, &st); vfile_send_data(fd, (int)sizeof(stat_buf), stat_buf, sizeof(stat_buf)); @@ -3593,7 +3475,7 @@ static void handle_packet(int fd, char *pkt) vfile_send_rc(fd, -1, errno); break; } - vfile_encode_stat(stat_buf, &st); + ela_gdb_vfile_encode_stat(stat_buf, &st); vfile_send_data(fd, (int)sizeof(stat_buf), stat_buf, sizeof(stat_buf)); @@ -3708,7 +3590,7 @@ static void handle_packet(int fd, char *pkt) /* -1 (all) and 0 (any) → keep current thread */ if (strcmp(tid_str, "-1") != 0 && strcmp(tid_str, "0") != 0) { - parse_thread_id(tid_str, NULL, &h_tid); + ela_gdb_parse_thread_id(tid_str, NULL, &h_tid); if (h_tid > 0) g_current_tid = h_tid; } @@ -3723,7 +3605,7 @@ static void handle_packet(int fd, char *pkt) char task_path[64]; struct stat tstat; - if (parse_thread_id(pkt + 1, NULL, &t_tid) < 0 || t_tid <= 0) { + if (ela_gdb_parse_thread_id(pkt + 1, NULL, &t_tid) < 0 || t_tid <= 0) { rsp_send_str(fd, "E01"); break; } @@ -3995,7 +3877,7 @@ static void handle_packet(int fd, char *pkt) ssize_t comm_len; char hex_out[32 + 1]; /* 16 bytes × 2 hex + NUL */ - if (parse_thread_id(pkt + 17, NULL, &qte_tid) < 0 || + if (ela_gdb_parse_thread_id(pkt + 17, NULL, &qte_tid) < 0 || qte_tid <= 0) { rsp_send_str(fd, ""); break; diff --git a/agent/linux/linux_gdbserver_pkt_util.c b/agent/linux/linux_gdbserver_pkt_util.c new file mode 100644 index 0000000..f7701ce --- /dev/null +++ b/agent/linux/linux_gdbserver_pkt_util.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-or-later - Copyright (c) 2026 Nicholas Starke + +#include "linux_gdbserver_pkt_util.h" + +#include +#include +#include +#include +#include +#include +#include + +/* ----------------------------------------------------------------------- + * Thread-id parser (handles both "p." and plain "") + * ---------------------------------------------------------------------- */ + +int ela_gdb_parse_thread_id(const char *s, pid_t *out_pid, pid_t *out_tid) +{ + unsigned long long p, t; + int n = 0; + + if (!s || !*s) + return -1; + + if (s[0] == 'p') { + if (sscanf(s + 1, "%llx.%llx%n", &p, &t, &n) == 2) { + if (out_pid) *out_pid = (pid_t)p; + if (out_tid) *out_tid = (pid_t)t; + return 1 + n; + } + return -1; + } + + if (sscanf(s, "%llx%n", &t, &n) == 1) { + if (out_pid) *out_pid = 0; + if (out_tid) *out_tid = (pid_t)t; + return n; + } + return -1; +} + +/* ----------------------------------------------------------------------- + * RSP binary-escape decoder + * ---------------------------------------------------------------------- */ + +int ela_gdb_rsp_binary_unescape(const char *src, size_t max_src, + uint8_t *dst, size_t expected) +{ + size_t in = 0, out = 0; + + while (out < expected) { + if (in >= max_src) + return -1; + if ((unsigned char)src[in] == 0x7du) { + in++; + if (in >= max_src) + return -1; + dst[out++] = (uint8_t)((unsigned char)src[in] ^ 0x20u); + } else { + dst[out++] = (uint8_t)(unsigned char)src[in]; + } + in++; + } + return (int)out; +} + +/* ----------------------------------------------------------------------- + * GDB vFile helpers + * ---------------------------------------------------------------------- */ + +static void vfile_put_be32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)(v); +} + +static void vfile_put_be64(uint8_t *p, uint64_t v) +{ + vfile_put_be32(p, (uint32_t)(v >> 32)); + vfile_put_be32(p + 4, (uint32_t)(v)); +} + +void ela_gdb_vfile_encode_stat(uint8_t *buf, const struct stat *st) +{ + memset(buf, 0, 64); + vfile_put_be32(buf + 0, (uint32_t)st->st_dev); + vfile_put_be32(buf + 4, (uint32_t)st->st_ino); + vfile_put_be32(buf + 8, (uint32_t)st->st_mode); + vfile_put_be32(buf + 12, (uint32_t)st->st_nlink); + vfile_put_be32(buf + 16, (uint32_t)st->st_uid); + vfile_put_be32(buf + 20, (uint32_t)st->st_gid); + vfile_put_be32(buf + 24, (uint32_t)st->st_rdev); + vfile_put_be64(buf + 28, (uint64_t)st->st_size); + vfile_put_be64(buf + 36, (uint64_t)st->st_blksize); + vfile_put_be64(buf + 44, (uint64_t)st->st_blocks); + vfile_put_be32(buf + 52, (uint32_t)st->st_atime); + vfile_put_be32(buf + 56, (uint32_t)st->st_mtime); + vfile_put_be32(buf + 60, (uint32_t)st->st_ctime); +} + +int ela_gdb_vfile_flags_to_linux(int gflags) +{ + int lflags = gflags & 3; /* O_RDONLY/O_WRONLY/O_RDWR values match */ + if (gflags & 0x008) lflags |= O_APPEND; + if (gflags & 0x200) lflags |= O_CREAT; + if (gflags & 0x400) lflags |= O_TRUNC; + if (gflags & 0x800) lflags |= O_EXCL; + return lflags; +} diff --git a/agent/linux/linux_gdbserver_pkt_util.h b/agent/linux/linux_gdbserver_pkt_util.h new file mode 100644 index 0000000..e7c4e79 --- /dev/null +++ b/agent/linux/linux_gdbserver_pkt_util.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-or-later - Copyright (c) 2026 Nicholas Starke + +#ifndef LINUX_GDBSERVER_PKT_UTIL_H +#define LINUX_GDBSERVER_PKT_UTIL_H + +#include +#include +#include +#include + +/* + * Parse an RSP thread-id from the string at s. Stores the parsed TID in + * *out_tid and (if the multiprocess "p." form is present) the PID + * in *out_pid; otherwise *out_pid is set to 0. Returns the number of input + * characters consumed, or -1 on parse error. Either output pointer may be + * NULL if the caller doesn't need that value. + */ +int ela_gdb_parse_thread_id(const char *s, pid_t *out_pid, pid_t *out_tid); + +/* + * Decode an RSP binary-escape sequence from src into dst. + * Escape byte 0x7d is followed by the escaped byte XOR'd with 0x20. + * Reads at most max_src bytes from src; writes exactly expected bytes to dst. + * Returns expected on success, -1 if src runs out before expected bytes are + * decoded. + */ +int ela_gdb_rsp_binary_unescape(const char *src, size_t max_src, + uint8_t *dst, size_t expected); + +/* + * Encode a host struct stat into the 64-byte GDB fio_stat structure. + * All fields are big-endian. buf must be at least 64 bytes. + */ +void ela_gdb_vfile_encode_stat(uint8_t *buf, const struct stat *st); + +/* + * Translate GDB fileio open flags to Linux open(2) flags. + * GDB uses its own constants (from gdb/fileio.h); Linux values differ. + */ +int ela_gdb_vfile_flags_to_linux(int gflags); + +#endif /* LINUX_GDBSERVER_PKT_UTIL_H */ diff --git a/tests/unit/agent/main.c b/tests/unit/agent/main.c index 5e0f018..3c86f3b 100644 --- a/tests/unit/agent/main.c +++ b/tests/unit/agent/main.c @@ -72,6 +72,7 @@ int run_uboot_validate_env_security_util_tests(void); int run_uboot_validate_cmdline_init_util_tests(void); int run_linux_process_watch_util_tests(void); int run_linux_gdbserver_util_tests(void); +int run_linux_gdbserver_pkt_util_tests(void); int main(void) { @@ -149,6 +150,7 @@ int main(void) rc |= run_uboot_validate_cmdline_init_util_tests(); rc |= run_linux_process_watch_util_tests(); rc |= run_linux_gdbserver_util_tests(); + rc |= run_linux_gdbserver_pkt_util_tests(); return rc; } diff --git a/tests/unit/agent/test_linux_gdbserver_pkt_util.c b/tests/unit/agent/test_linux_gdbserver_pkt_util.c new file mode 100644 index 0000000..81ef3fa --- /dev/null +++ b/tests/unit/agent/test_linux_gdbserver_pkt_util.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-3.0-or-later - Copyright (c) 2026 Nicholas Starke + +#include "test_harness.h" +#include "../../../agent/linux/linux_gdbserver_pkt_util.h" + +#include +#include +#include +#include + +/* ========================================================================= + * ela_gdb_parse_thread_id + * ====================================================================== */ + +static void test_parse_thread_id_plain(void) +{ + pid_t pid = 99, tid = 99; + int n; + + /* plain hex TID, no multiprocess prefix */ + n = ela_gdb_parse_thread_id("1a", &pid, &tid); + ELA_ASSERT_INT_EQ(2, n); + ELA_ASSERT_INT_EQ(0, (int)pid); + ELA_ASSERT_INT_EQ(26, (int)tid); +} + +static void test_parse_thread_id_multiprocess(void) +{ + pid_t pid = 0, tid = 0; + int n; + + /* p. multiprocess form */ + n = ela_gdb_parse_thread_id("p1234.5678", &pid, &tid); + ELA_ASSERT_INT_EQ(10, n); + ELA_ASSERT_INT_EQ(0x1234, (int)pid); + ELA_ASSERT_INT_EQ(0x5678, (int)tid); +} + +static void test_parse_thread_id_null_outputs(void) +{ + /* Both output pointers NULL — should not crash */ + int n = ela_gdb_parse_thread_id("ff", NULL, NULL); + + ELA_ASSERT_INT_EQ(2, n); +} + +static void test_parse_thread_id_null_input(void) +{ + pid_t pid, tid; + + ELA_ASSERT_INT_EQ(-1, ela_gdb_parse_thread_id(NULL, &pid, &tid)); +} + +static void test_parse_thread_id_empty(void) +{ + pid_t pid, tid; + + ELA_ASSERT_INT_EQ(-1, ela_gdb_parse_thread_id("", &pid, &tid)); +} + +static void test_parse_thread_id_p_no_dot(void) +{ + pid_t pid, tid; + + /* 'p' prefix but missing dot — invalid */ + ELA_ASSERT_INT_EQ(-1, ela_gdb_parse_thread_id("p1234", &pid, &tid)); +} + +static void test_parse_thread_id_multiprocess_only_pid(void) +{ + pid_t pid = 0, tid = 0; + int n; + + n = ela_gdb_parse_thread_id("pff.1", &pid, &tid); + ELA_ASSERT_INT_EQ(5, n); + ELA_ASSERT_INT_EQ(0xff, (int)pid); + ELA_ASSERT_INT_EQ(1, (int)tid); +} + +static void test_parse_thread_id_plain_1(void) +{ + pid_t pid = 99, tid = 0; + int n; + + n = ela_gdb_parse_thread_id("1", &pid, &tid); + ELA_ASSERT_INT_EQ(1, n); + ELA_ASSERT_INT_EQ(0, (int)pid); + ELA_ASSERT_INT_EQ(1, (int)tid); +} + +/* ========================================================================= + * ela_gdb_rsp_binary_unescape + * ====================================================================== */ + +static void test_binary_unescape_plain(void) +{ + const char src[] = "abc"; + uint8_t dst[4]; + int n; + + n = ela_gdb_rsp_binary_unescape(src, sizeof(src) - 1, dst, 3); + ELA_ASSERT_INT_EQ(3, n); + ELA_ASSERT_INT_EQ('a', (int)dst[0]); + ELA_ASSERT_INT_EQ('b', (int)dst[1]); + ELA_ASSERT_INT_EQ('c', (int)dst[2]); +} + +static void test_binary_unescape_escape_sequence(void) +{ + /* 0x7d 0x5d decodes to 0x5d ^ 0x20 = 0x7d (the escape byte itself) */ + const uint8_t src[] = { 0x7d, 0x5d }; + uint8_t dst[2]; + int n; + + n = ela_gdb_rsp_binary_unescape((const char *)src, 2, dst, 1); + ELA_ASSERT_INT_EQ(1, n); + ELA_ASSERT_INT_EQ(0x7d, (int)dst[0]); +} + +static void test_binary_unescape_escape_hash(void) +{ + /* '#' (0x23) is escaped as 0x7d 0x03; 0x03 ^ 0x20 = 0x23 */ + const uint8_t src[] = { 0x7d, 0x03 }; + uint8_t dst[2]; + int n; + + n = ela_gdb_rsp_binary_unescape((const char *)src, 2, dst, 1); + ELA_ASSERT_INT_EQ(1, n); + ELA_ASSERT_INT_EQ('#', (int)dst[0]); +} + +static void test_binary_unescape_src_exhausted(void) +{ + /* max_src too small to produce expected bytes */ + const char src[] = "ab"; + uint8_t dst[4]; + + ELA_ASSERT_INT_EQ(-1, + ela_gdb_rsp_binary_unescape(src, 2, dst, 3)); +} + +static void test_binary_unescape_escape_at_end(void) +{ + /* escape byte (0x7d) at end with no following byte — should fail */ + const uint8_t src[] = { 0x7d }; + uint8_t dst[2]; + + ELA_ASSERT_INT_EQ(-1, + ela_gdb_rsp_binary_unescape((const char *)src, 1, dst, 1)); +} + +static void test_binary_unescape_zero_expected(void) +{ + /* requesting 0 output bytes should always succeed immediately */ + const char src[] = "xyz"; + uint8_t dst[4]; + int n; + + n = ela_gdb_rsp_binary_unescape(src, sizeof(src) - 1, dst, 0); + ELA_ASSERT_INT_EQ(0, n); +} + +static void test_binary_unescape_mixed(void) +{ + /* 'A' 0x7d 0x04 'B' → 'A' '$' 'B' (0x04^0x20=0x24='$') */ + const uint8_t src[] = { 'A', 0x7d, 0x04, 'B' }; + uint8_t dst[4]; + int n; + + n = ela_gdb_rsp_binary_unescape((const char *)src, 4, dst, 3); + ELA_ASSERT_INT_EQ(3, n); + ELA_ASSERT_INT_EQ('A', (int)dst[0]); + ELA_ASSERT_INT_EQ('$', (int)dst[1]); + ELA_ASSERT_INT_EQ('B', (int)dst[2]); +} + +/* ========================================================================= + * ela_gdb_vfile_encode_stat + * ====================================================================== */ + +/* + * Helper: read a 32-bit big-endian value from buf at offset off. + */ +static uint32_t read_be32(const uint8_t *buf, size_t off) +{ + return ((uint32_t)buf[off] << 24) | + ((uint32_t)buf[off+1] << 16) | + ((uint32_t)buf[off+2] << 8) | + (uint32_t)buf[off+3]; +} + +static uint64_t read_be64(const uint8_t *buf, size_t off) +{ + return ((uint64_t)read_be32(buf, off) << 32) | + (uint64_t)read_be32(buf, off + 4); +} + +static void test_vfile_encode_stat_zeroes_buffer(void) +{ + struct stat st; + uint8_t buf[64]; + + memset(&st, 0, sizeof(st)); + memset(buf, 0xff, sizeof(buf)); + ela_gdb_vfile_encode_stat(buf, &st); + + /* All fields zero → whole buffer should be zero */ + ELA_ASSERT_TRUE(read_be64(buf, 28) == 0); /* st_size */ + ELA_ASSERT_TRUE(read_be32(buf, 52) == 0); /* st_atime */ +} + +static void test_vfile_encode_stat_dev(void) +{ + struct stat st; + uint8_t buf[64]; + + memset(&st, 0, sizeof(st)); + st.st_dev = 0x12345678u; + ela_gdb_vfile_encode_stat(buf, &st); + ELA_ASSERT_TRUE(read_be32(buf, 0) == 0x12345678u); +} + +static void test_vfile_encode_stat_mode(void) +{ + struct stat st; + uint8_t buf[64]; + + memset(&st, 0, sizeof(st)); + st.st_mode = 0100755u; /* regular file, rwxr-xr-x */ + ela_gdb_vfile_encode_stat(buf, &st); + ELA_ASSERT_TRUE(read_be32(buf, 8) == 0100755u); +} + +static void test_vfile_encode_stat_size(void) +{ + struct stat st; + uint8_t buf[64]; + + memset(&st, 0, sizeof(st)); + st.st_size = UINT64_C(0x0102030405060708); + ela_gdb_vfile_encode_stat(buf, &st); + ELA_ASSERT_TRUE(read_be64(buf, 28) == UINT64_C(0x0102030405060708)); +} + +static void test_vfile_encode_stat_timestamps(void) +{ + struct stat st; + uint8_t buf[64]; + + memset(&st, 0, sizeof(st)); + st.st_atime = 0xaabbccdd; + st.st_mtime = 0x11223344; + st.st_ctime = 0xdeadbeef; + ela_gdb_vfile_encode_stat(buf, &st); + ELA_ASSERT_TRUE(read_be32(buf, 52) == 0xaabbccddu); + ELA_ASSERT_TRUE(read_be32(buf, 56) == 0x11223344u); + ELA_ASSERT_TRUE(read_be32(buf, 60) == 0xdeadbeefu); +} + +/* ========================================================================= + * ela_gdb_vfile_flags_to_linux + * ====================================================================== */ + +static void test_vfile_flags_rdonly(void) +{ + /* GDB O_RDONLY = 0 */ + ELA_ASSERT_INT_EQ(0, ela_gdb_vfile_flags_to_linux(0)); +} + +static void test_vfile_flags_wronly(void) +{ + /* GDB O_WRONLY = 1; Linux O_WRONLY = 1 */ + ELA_ASSERT_INT_EQ(1, ela_gdb_vfile_flags_to_linux(1)); +} + +static void test_vfile_flags_rdwr(void) +{ + /* GDB O_RDWR = 2; Linux O_RDWR = 2 */ + ELA_ASSERT_INT_EQ(2, ela_gdb_vfile_flags_to_linux(2)); +} + +static void test_vfile_flags_append(void) +{ + int lflags = ela_gdb_vfile_flags_to_linux(0x008); + + ELA_ASSERT_TRUE((lflags & O_APPEND) != 0); +} + +static void test_vfile_flags_creat(void) +{ + int lflags = ela_gdb_vfile_flags_to_linux(0x200); + + ELA_ASSERT_TRUE((lflags & O_CREAT) != 0); +} + +static void test_vfile_flags_trunc(void) +{ + int lflags = ela_gdb_vfile_flags_to_linux(0x400); + + ELA_ASSERT_TRUE((lflags & O_TRUNC) != 0); +} + +static void test_vfile_flags_excl(void) +{ + int lflags = ela_gdb_vfile_flags_to_linux(0x800); + + ELA_ASSERT_TRUE((lflags & O_EXCL) != 0); +} + +static void test_vfile_flags_combined(void) +{ + /* GDB O_WRONLY | O_CREAT | O_TRUNC = 0x601 */ + int lflags = ela_gdb_vfile_flags_to_linux(0x601); + + ELA_ASSERT_TRUE((lflags & 1) != 0); /* O_WRONLY */ + ELA_ASSERT_TRUE((lflags & O_CREAT) != 0); + ELA_ASSERT_TRUE((lflags & O_TRUNC) != 0); +} + +/* ========================================================================= + * Test suite registration + * ====================================================================== */ + +int run_linux_gdbserver_pkt_util_tests(void) +{ + static const struct ela_test_case cases[] = { + { "parse_thread_id/plain", test_parse_thread_id_plain }, + { "parse_thread_id/multiprocess", test_parse_thread_id_multiprocess }, + { "parse_thread_id/null_outputs", test_parse_thread_id_null_outputs }, + { "parse_thread_id/null_input", test_parse_thread_id_null_input }, + { "parse_thread_id/empty", test_parse_thread_id_empty }, + { "parse_thread_id/p_no_dot", test_parse_thread_id_p_no_dot }, + { "parse_thread_id/mp_only_pid", test_parse_thread_id_multiprocess_only_pid }, + { "parse_thread_id/plain_1", test_parse_thread_id_plain_1 }, + { "rsp_binary_unescape/plain", test_binary_unescape_plain }, + { "rsp_binary_unescape/escape_self", test_binary_unescape_escape_sequence }, + { "rsp_binary_unescape/escape_hash", test_binary_unescape_escape_hash }, + { "rsp_binary_unescape/src_exhausted", test_binary_unescape_src_exhausted }, + { "rsp_binary_unescape/escape_at_end", test_binary_unescape_escape_at_end }, + { "rsp_binary_unescape/zero_expected", test_binary_unescape_zero_expected }, + { "rsp_binary_unescape/mixed", test_binary_unescape_mixed }, + { "vfile_encode_stat/zeroes_buffer", test_vfile_encode_stat_zeroes_buffer }, + { "vfile_encode_stat/dev", test_vfile_encode_stat_dev }, + { "vfile_encode_stat/mode", test_vfile_encode_stat_mode }, + { "vfile_encode_stat/size", test_vfile_encode_stat_size }, + { "vfile_encode_stat/timestamps", test_vfile_encode_stat_timestamps }, + { "vfile_flags_to_linux/rdonly", test_vfile_flags_rdonly }, + { "vfile_flags_to_linux/wronly", test_vfile_flags_wronly }, + { "vfile_flags_to_linux/rdwr", test_vfile_flags_rdwr }, + { "vfile_flags_to_linux/append", test_vfile_flags_append }, + { "vfile_flags_to_linux/creat", test_vfile_flags_creat }, + { "vfile_flags_to_linux/trunc", test_vfile_flags_trunc }, + { "vfile_flags_to_linux/excl", test_vfile_flags_excl }, + { "vfile_flags_to_linux/combined", test_vfile_flags_combined }, + }; + + return ela_run_test_suite("linux_gdbserver_pkt_util", cases, + sizeof(cases) / sizeof(cases[0])); +} From 88911aed723bb76ad5b6d99c95585988b233d556 Mon Sep 17 00:00:00 2001 From: nstarke Date: Sat, 21 Mar 2026 22:21:27 -0500 Subject: [PATCH 4/4] increasing code coverage for process watch --- agent/linux/linux_process_watch_cmd.c | 11 +-- agent/linux/linux_process_watch_util.c | 9 ++ agent/linux/linux_process_watch_util.h | 7 ++ .../agent/test_linux_process_watch_util.c | 85 +++++++++++++++++++ 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/agent/linux/linux_process_watch_cmd.c b/agent/linux/linux_process_watch_cmd.c index 6808a8c..178ed60 100644 --- a/agent/linux/linux_process_watch_cmd.c +++ b/agent/linux/linux_process_watch_cmd.c @@ -247,15 +247,6 @@ static int scan_pids_for_needle(const char *needle, * Output helpers (daemon-side) * ====================================================================== */ -static const char *event_content_type(const char *fmt) -{ - if (!strcmp(fmt, "json")) - return "application/json; charset=utf-8"; - if (!strcmp(fmt, "csv")) - return "text/csv; charset=utf-8"; - return "text/plain; charset=utf-8"; -} - static void emit_event(const char *needle, const char *old_pids, const char *new_pids, const char *fmt) @@ -279,7 +270,7 @@ static void emit_event(const char *needle, "process_watch", NULL); if (uri) { (void)ela_http_post(uri, (const uint8_t *)buf, len, - event_content_type(fmt), + ela_process_watch_content_type(fmt), g_watch_insecure, false, errbuf, sizeof(errbuf)); free(uri); diff --git a/agent/linux/linux_process_watch_util.c b/agent/linux/linux_process_watch_util.c index 0d93b62..c1bcf71 100644 --- a/agent/linux/linux_process_watch_util.c +++ b/agent/linux/linux_process_watch_util.c @@ -213,3 +213,12 @@ int ela_process_watch_format_list_entry(const char *needle, NULL, NULL, fmt, out, out_len); } + +const char *ela_process_watch_content_type(const char *fmt) +{ + if (fmt && !strcmp(fmt, "json")) + return "application/json; charset=utf-8"; + if (fmt && !strcmp(fmt, "csv")) + return "text/csv; charset=utf-8"; + return "text/plain; charset=utf-8"; +} diff --git a/agent/linux/linux_process_watch_util.h b/agent/linux/linux_process_watch_util.h index 0272516..ee82f14 100644 --- a/agent/linux/linux_process_watch_util.h +++ b/agent/linux/linux_process_watch_util.h @@ -76,4 +76,11 @@ int ela_process_watch_format_list_entry(const char *needle, char **out, size_t *out_len); +/* + * Return the HTTP Content-Type header value for a given output format. + * fmt may be "json", "csv", or anything else (treated as plain text). + * Never returns NULL. + */ +const char *ela_process_watch_content_type(const char *fmt); + #endif /* LINUX_PROCESS_WATCH_UTIL_H */ diff --git a/tests/unit/agent/test_linux_process_watch_util.c b/tests/unit/agent/test_linux_process_watch_util.c index 697ec87..b4c711f 100644 --- a/tests/unit/agent/test_linux_process_watch_util.c +++ b/tests/unit/agent/test_linux_process_watch_util.c @@ -98,6 +98,21 @@ static void test_parse_small_needle_buf_returns_minus1(void) ELA_ASSERT_INT_EQ(-1, ela_process_watch_state_parse_line("sshd\t1234\n", n, sizeof(n), p, sizeof(p))); } +static void test_parse_small_pids_buf_returns_minus1(void) +{ + char n[64], p[3]; /* "1234" won't fit in 3 bytes */ + + ELA_ASSERT_INT_EQ(-1, ela_process_watch_state_parse_line("sshd\t1234\n", n, sizeof(n), p, sizeof(p))); +} + +static void test_parse_empty_needle_returns_minus1(void) +{ + char n[64], p[64]; + + /* Tab at position 0 means zero-length needle — invalid */ + ELA_ASSERT_INT_EQ(-1, ela_process_watch_state_parse_line("\t1234\n", n, sizeof(n), p, sizeof(p))); +} + static void test_parse_valid_line_with_single_pid(void) { char n[64], p[64]; @@ -168,6 +183,13 @@ static void test_format_small_buf_returns_minus1(void) ELA_ASSERT_INT_EQ(-1, ela_process_watch_state_format_line("sshd", "1234", out, sizeof(out))); } +static void test_format_empty_needle_returns_minus1(void) +{ + char out[64]; + + ELA_ASSERT_INT_EQ(-1, ela_process_watch_state_format_line("", "1234", out, sizeof(out))); +} + static void test_format_valid_produces_tab_separated_line(void) { char out[128]; @@ -387,6 +409,58 @@ static void test_list_empty_pids_ok(void) free(out); } +/* ========================================================================= + * ela_process_watch_content_type + * ====================================================================== */ + +static void test_content_type_json(void) +{ + ELA_ASSERT_STR_EQ("application/json; charset=utf-8", + ela_process_watch_content_type("json")); +} + +static void test_content_type_csv(void) +{ + ELA_ASSERT_STR_EQ("text/csv; charset=utf-8", + ela_process_watch_content_type("csv")); +} + +static void test_content_type_txt(void) +{ + ELA_ASSERT_STR_EQ("text/plain; charset=utf-8", + ela_process_watch_content_type("txt")); +} + +static void test_content_type_unknown(void) +{ + ELA_ASSERT_STR_EQ("text/plain; charset=utf-8", + ela_process_watch_content_type("xml")); +} + +static void test_content_type_null(void) +{ + ELA_ASSERT_STR_EQ("text/plain; charset=utf-8", + ela_process_watch_content_type(NULL)); +} + +/* ========================================================================= + * Round-trip: format_line then state_parse_line + * ====================================================================== */ + +static void test_roundtrip_format_then_parse(void) +{ + char line[256]; + char needle_out[64], pids_out[64]; + + ELA_ASSERT_INT_EQ(0, ela_process_watch_state_format_line( + "nginx", "100,200,300", line, sizeof(line))); + ELA_ASSERT_INT_EQ(0, ela_process_watch_state_parse_line( + line, needle_out, sizeof(needle_out), + pids_out, sizeof(pids_out))); + ELA_ASSERT_STR_EQ("nginx", needle_out); + ELA_ASSERT_STR_EQ("100,200,300", pids_out); +} + /* ========================================================================= * Suite registration * ====================================================================== */ @@ -409,6 +483,8 @@ int run_linux_process_watch_util_tests(void) { "parse/null_pids_out", test_parse_null_pids_out_returns_minus1 }, { "parse/no_tab", test_parse_no_tab_returns_minus1 }, { "parse/small_needle_buf", test_parse_small_needle_buf_returns_minus1 }, + { "parse/small_pids_buf", test_parse_small_pids_buf_returns_minus1 }, + { "parse/empty_needle", test_parse_empty_needle_returns_minus1 }, { "parse/single_pid", test_parse_valid_line_with_single_pid }, { "parse/multiple_pids", test_parse_valid_line_with_multiple_pids }, { "parse/empty_pids", test_parse_valid_line_empty_pids }, @@ -418,6 +494,7 @@ int run_linux_process_watch_util_tests(void) { "format_line/null_pids", test_format_null_pids_returns_minus1 }, { "format_line/null_out", test_format_null_out_returns_minus1 }, { "format_line/small_buf", test_format_small_buf_returns_minus1 }, + { "format_line/empty_needle", test_format_empty_needle_returns_minus1 }, { "format_line/valid", test_format_valid_produces_tab_separated_line }, { "format_line/empty_pids", test_format_empty_pids_allowed }, /* ela_process_watch_pids_equal */ @@ -443,6 +520,14 @@ int run_linux_process_watch_util_tests(void) { "list/csv", test_list_csv_format }, { "list/json", test_list_json_format }, { "list/empty_pids", test_list_empty_pids_ok }, + /* ela_process_watch_content_type */ + { "content_type/json", test_content_type_json }, + { "content_type/csv", test_content_type_csv }, + { "content_type/txt", test_content_type_txt }, + { "content_type/unknown", test_content_type_unknown }, + { "content_type/null", test_content_type_null }, + /* roundtrip */ + { "roundtrip/format_parse", test_roundtrip_format_then_parse }, }; return ela_run_test_suite("linux_process_watch_util",