From 6b0e44b694bef940d671981f59bd4d33414f8f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=85=D8=B5=D8=B7=D9=81=D9=8A=20=D9=85=D8=AD=D9=85=D9=88?= =?UTF-8?q?=D8=AF=20=D9=83=D9=85=D8=A7=D9=84=20=D8=A7=D9=84=D8=AF=D9=8A?= =?UTF-8?q?=D9=86?= <48567303+moste00@users.noreply.github.com> Date: Mon, 29 Jun 2026 02:05:16 +0300 Subject: [PATCH 1/3] implement file download from the remote machine in GDB protocol --- librz/include/rz_io.h | 1 + librz/io/p/io_gdb.c | 64 ++++++++++++++++++- librz/main/rizin.c | 48 +++++++++++++- .../rzgdb/include/gdbclient/commands.h | 1 + subprojects/rzgdb/src/gdbclient/core.c | 8 ++- 5 files changed, 117 insertions(+), 5 deletions(-) diff --git a/librz/include/rz_io.h b/librz/include/rz_io.h index 512885851bd..261bc59eb45 100644 --- a/librz/include/rz_io.h +++ b/librz/include/rz_io.h @@ -384,6 +384,7 @@ RZ_API bool rz_io_desc_is_blockdevice(RzIODesc *desc); RZ_API bool rz_io_desc_is_chardevice(RzIODesc *desc); RZ_API bool rz_io_desc_exchange(RzIO *io, int fd, int fdx); // this should get 2 descs RZ_API bool rz_io_desc_is_dbg(RzIODesc *desc); +RZ_API bool rz_io_gdb_download_file(RzIODesc *desc, const char *remote, const char *local); RZ_API int rz_io_desc_get_pid(RzIODesc *desc); RZ_API int rz_io_desc_get_tid(RzIODesc *desc); RZ_API bool rz_io_desc_get_base(RzIODesc *desc, ut64 *base); diff --git a/librz/io/p/io_gdb.c b/librz/io/p/io_gdb.c index 3a4039aef8b..53b6e542bce 100644 --- a/librz/io/p/io_gdb.c +++ b/librz/io/p/io_gdb.c @@ -207,6 +207,56 @@ static int __gettid(RzIODesc *fd) { extern int send_msg(libgdbr_t *g, const char *command); extern int read_packet(libgdbr_t *instance, bool vcont); +static bool gdbr_download_file(libgdbr_t *g, const char *remote, const char *local) { + rz_return_val_if_fail(g && remote && local, false); + + if (!rz_file_dump(local, NULL, 0, false)) { + return false; + } + + if (gdbr_open_file(g, remote, O_RDONLY, 0) < 0) { + return false; + } + + uint32_t off = 0; + bool ok = true; + ut32 chunk = RZ_MAX(64, g->stub_features.pkt_sz / 2); + chunk = RZ_MIN(chunk, 65536); + ut8 *buf = malloc(chunk); + if (!buf) { + gdbr_close_file(g); + return false; + } + + int ret; + do { + ret = gdbr_pread_file(g, buf, chunk, off); + if (ret < 0) { + ok = false; + break; + } + if (ret == 0) { + break; + } + if (!rz_file_dump(local, buf, ret, true)) { + ok = false; + break; + } + off += ret; + } while ((ut32)ret == chunk); + free(buf); + gdbr_close_file(g); + return ok; +} + +RZ_API bool rz_io_gdb_download_file(RzIODesc *fd, const char *remote, const char *local) { + rz_return_val_if_fail(fd && remote && local, false); + if (!fd->plugin || strcmp(fd->plugin->name, "gdb")) { + return false; + } + return gdbr_download_file(fd->data, remote, local); +} + static char *__system(RzIO *io, RzIODesc *fd, const char *cmd) { if (!fd || !fd->data) { return NULL; @@ -228,10 +278,22 @@ static char *__system(RzIO *io, RzIODesc *fd, const char *cmd) { " R! pktsz - get max packet size used\n" " R! pktsz bytes - set max. packet size as 'bytes' bytes\n" " R! exec_file [pid] - get file which was executed for" - " current/specified pid\n"); + " current/specified pid\n" + " R! download_file remote local - download remote file to local path\n"); return NULL; } libgdbr_t *desc = fd->data; + if (rz_str_startswith(cmd, "download_file")) { + int argc = 0; + char **argv = rz_str_argv(cmd, &argc); + if (!argv || argc != 3) { + rz_str_argv_free(argv); + return rz_str_dup("0"); + } + bool ok = gdbr_download_file(desc, argv[1], argv[2]); + rz_str_argv_free(argv); + return rz_str_dup(ok ? "1" : "0"); + } if (rz_str_startswith(cmd, "pktsz")) { const char *ptr = rz_str_trim_head_ro(cmd + 5); if (!isdigit((ut8)*ptr)) { diff --git a/librz/main/rizin.c b/librz/main/rizin.c index 380ea640e10..0644ca5a4d2 100644 --- a/librz/main/rizin.c +++ b/librz/main/rizin.c @@ -20,6 +20,24 @@ static bool is_valid_dmp_file(RzCoreFile *fh) { return d && strncmp(d->name, "dmp://", 6); } +static char *download_gdb_remote_file(RzIODesc *iod, const char *remote_path) { + if (!iod || RZ_STR_ISEMPTY(remote_path)) { + return NULL; + } + char *local_path = NULL; + int fd = rz_file_mkstemp("gdbexe", &local_path); + if (fd == -1 || !local_path) { + free(local_path); + return NULL; + } + close(fd); + if (!rz_io_gdb_download_file(iod, remote_path, local_path)) { + rz_file_rm(local_path); + RZ_FREE(local_path); + } + return local_path; +} + static char *get_file_in_cur_dir(const char *filepath) { filepath = rz_file_basename(filepath); if (rz_file_exists(filepath) && !rz_file_is_directory(filepath)) { @@ -428,6 +446,7 @@ RZ_API int rz_main_rizin(int argc, const char **argv) { bool do_list_io_plugins = false; char *file = NULL; char *pfile = NULL; + char *gdb_downloaded_exe = NULL; const char *asmarch = NULL; const char *asmos = NULL; const char *forcebin = NULL; @@ -1073,12 +1092,33 @@ RZ_API int rz_main_rizin(int argc, const char **argv) { } } else if (is_valid_gdb_file(fh) || is_valid_dmp_file(fh)) { filepath = iod->name; - if (RZ_STR_ISNOTEMPTY(filepath) && rz_file_exists(filepath) && !rz_file_is_directory(filepath)) { + bool is_gdb_remote = rz_str_startswith(pfile, "gdb://"); + bool is_localhost_gdb_remote = rz_str_startswith(pfile, "gdb://localhost:"); + bool local_file_exists = RZ_STR_ISNOTEMPTY(filepath) && rz_file_exists(filepath) && !rz_file_is_directory(filepath); + bool should_download = is_gdb_remote && RZ_STR_ISNOTEMPTY(filepath) && (!is_localhost_gdb_remote || !local_file_exists); + if (should_download) { + char *downloaded = NULL; + if (addr == UINT64_MAX) { + addr = rz_debug_get_baddr(r->dbg, filepath); + } + if (rz_cons_yesno('n', "Download remote executable '%s' to a temporary file? (y/N) ", filepath)) { + downloaded = download_gdb_remote_file(iod, filepath); + if (!downloaded) { + RZ_LOG_ERROR("Failed to download remote executable '%s'.\n", filepath); + } + } + if (downloaded) { + gdb_downloaded_exe = downloaded; + free(iod->name); + iod->name = rz_str_dup(downloaded); + rz_core_bin_load(r, NULL, addr); + } + } else if (local_file_exists) { if (addr == UINT64_MAX) { addr = rz_debug_get_baddr(r->dbg, filepath); } rz_core_bin_load(r, filepath, addr); - } else if ((filepath = get_file_in_cur_dir(filepath))) { + } else if (!is_gdb_remote && (filepath = get_file_in_cur_dir(filepath))) { // Present in local directory if (iod) { free(iod->name); @@ -1574,6 +1614,10 @@ RZ_API int rz_main_rizin(int argc, const char **argv) { // execution. // rz_core_file_close (r, fh); rz_core_free(r); + if (gdb_downloaded_exe) { + rz_file_rm(gdb_downloaded_exe); + free(gdb_downloaded_exe); + } rz_cons_set_raw(0); rz_cons_free(); LISTS_FREE(); diff --git a/subprojects/rzgdb/include/gdbclient/commands.h b/subprojects/rzgdb/include/gdbclient/commands.h index 49e2c9fa718..4fa409248fc 100644 --- a/subprojects/rzgdb/include/gdbclient/commands.h +++ b/subprojects/rzgdb/include/gdbclient/commands.h @@ -126,6 +126,7 @@ int gdbr_remove_hwa(libgdbr_t *g, ut64 address, int sizebp); * File read from remote target (only one file open at a time for now) */ int gdbr_open_file(libgdbr_t *g, const char *filename, int flags, int mode); +int gdbr_pread_file(libgdbr_t *g, ut8 *buf, ut64 max_len, uint32_t start_offset); int gdbr_read_file(libgdbr_t *g, ut8 *buf, ut64 max_len); int gdbr_close_file(libgdbr_t *g); diff --git a/subprojects/rzgdb/src/gdbclient/core.c b/subprojects/rzgdb/src/gdbclient/core.c index 7a30aad11e1..296041f47c5 100644 --- a/subprojects/rzgdb/src/gdbclient/core.c +++ b/subprojects/rzgdb/src/gdbclient/core.c @@ -1507,7 +1507,7 @@ int gdbr_open_file(libgdbr_t *g, const char *filename, int flags, int mode) { return ret; } -int gdbr_read_file(libgdbr_t *g, ut8 *buf, ut64 max_len) { +int gdbr_pread_file(libgdbr_t *g, ut8 *buf, ut64 max_len, uint32_t start_offset) { int ret, ret1; char command[64]; ut64 data_sz; @@ -1534,7 +1534,7 @@ int gdbr_read_file(libgdbr_t *g, ut8 *buf, ut64 max_len) { if (snprintf(command, sizeof(command) - 1, "vFile:pread:%x,%" PFMT64x ",%" PFMT64x, (int)g->remote_file_fd, (ut64)RZ_MIN(data_sz, max_len - ret), - (ut64)ret) < 0) { + (ut64)start_offset + ret) < 0) { ret = -1; goto end; } @@ -1561,6 +1561,10 @@ int gdbr_read_file(libgdbr_t *g, ut8 *buf, ut64 max_len) { return ret; } +int gdbr_read_file(libgdbr_t *g, ut8 *buf, ut64 max_len) { + return gdbr_pread_file(g, buf, max_len, 0); +} + int gdbr_close_file(libgdbr_t *g) { int ret = -1; char buf[64]; From bf3bb29be8c4628edb043834981e055284bfe4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=85=D8=B5=D8=B7=D9=81=D9=8A=20=D9=85=D8=AD=D9=85=D9=88?= =?UTF-8?q?=D8=AF=20=D9=83=D9=85=D8=A7=D9=84=20=D8=A7=D9=84=D8=AF=D9=8A?= =?UTF-8?q?=D9=86?= <48567303+moste00@users.noreply.github.com> Date: Wed, 1 Jul 2026 03:14:18 +0300 Subject: [PATCH 2/3] add tests --- test/db/archos/linux-x64/dbg_gdbserver | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/db/archos/linux-x64/dbg_gdbserver b/test/db/archos/linux-x64/dbg_gdbserver index 2114a89662b..3036ae532b0 100644 --- a/test/db/archos/linux-x64/dbg_gdbserver +++ b/test/db/archos/linux-x64/dbg_gdbserver @@ -26,3 +26,19 @@ EXPECT=< Date: Sat, 4 Jul 2026 22:37:42 +0300 Subject: [PATCH 3/3] refactor and review comments --- librz/include/rz_io.h | 3 ++- librz/io/io_desc.c | 7 +++++++ librz/io/p/io_gdb.c | 18 +++++++++++------- librz/main/rizin.c | 2 +- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/librz/include/rz_io.h b/librz/include/rz_io.h index 261bc59eb45..1d7fd597528 100644 --- a/librz/include/rz_io.h +++ b/librz/include/rz_io.h @@ -138,6 +138,7 @@ typedef struct rz_io_plugin_t { int (*create)(RzIO *io, const char *file, int mode, int type); bool (*check)(RzIO *io, const char *, bool many); ut8 *(*get_buf)(RzIODesc *desc, ut64 *size); + bool (*download_file)(RzIO *io, RzIODesc *fd, const char *remote, const char *local); } RzIOPlugin; typedef struct rz_io_map_t { @@ -384,7 +385,7 @@ RZ_API bool rz_io_desc_is_blockdevice(RzIODesc *desc); RZ_API bool rz_io_desc_is_chardevice(RzIODesc *desc); RZ_API bool rz_io_desc_exchange(RzIO *io, int fd, int fdx); // this should get 2 descs RZ_API bool rz_io_desc_is_dbg(RzIODesc *desc); -RZ_API bool rz_io_gdb_download_file(RzIODesc *desc, const char *remote, const char *local); +RZ_API bool rz_io_desc_download_file(RzIODesc *desc, const char *remote, const char *local); RZ_API int rz_io_desc_get_pid(RzIODesc *desc); RZ_API int rz_io_desc_get_tid(RzIODesc *desc); RZ_API bool rz_io_desc_get_base(RzIODesc *desc, ut64 *base); diff --git a/librz/io/io_desc.c b/librz/io/io_desc.c index 4ebe324832c..31edee4b054 100644 --- a/librz/io/io_desc.c +++ b/librz/io/io_desc.c @@ -307,6 +307,13 @@ RZ_API bool rz_io_desc_is_dbg(RzIODesc *desc) { return false; } +RZ_API bool rz_io_desc_download_file(RzIODesc *desc, const char *remote, const char *local) { + if (!desc || !desc->plugin || !desc->plugin->download_file || RZ_STR_ISEMPTY(remote) || RZ_STR_ISEMPTY(local)) { + return false; + } + return desc->plugin->download_file(desc->io, desc, remote, local); +} + RZ_API int rz_io_desc_get_pid(RzIODesc *desc) { //-1 and -2 are reserved if (!desc) { diff --git a/librz/io/p/io_gdb.c b/librz/io/p/io_gdb.c index 53b6e542bce..f10d9fb3a3c 100644 --- a/librz/io/p/io_gdb.c +++ b/librz/io/p/io_gdb.c @@ -15,6 +15,9 @@ #define RZ_GDB_MAGIC rz_str_djb2_hash("gdb") +#define MIN_DOWNLOAD_CHUNK 64 +#define MAX_DOWNLOAD_CHUNK 65536 + static int __close(RzIODesc *fd); static bool __plugin_open(RzIO *io, const char *file, bool many) { @@ -209,19 +212,20 @@ extern int read_packet(libgdbr_t *instance, bool vcont); static bool gdbr_download_file(libgdbr_t *g, const char *remote, const char *local) { rz_return_val_if_fail(g && remote && local, false); - + // empty the destination file by writing nothing in non-append mode if (!rz_file_dump(local, NULL, 0, false)) { return false; } - + // open the remote file on the stub side if (gdbr_open_file(g, remote, O_RDONLY, 0) < 0) { return false; } uint32_t off = 0; bool ok = true; - ut32 chunk = RZ_MAX(64, g->stub_features.pkt_sz / 2); - chunk = RZ_MIN(chunk, 65536); + // clamp the download size between a reasonable MIN floor and MAX ceiling + ut32 chunk = RZ_MAX(MIN_DOWNLOAD_CHUNK, g->stub_features.pkt_sz / 2); + chunk = RZ_MIN(chunk, MAX_DOWNLOAD_CHUNK); ut8 *buf = malloc(chunk); if (!buf) { gdbr_close_file(g); @@ -249,9 +253,8 @@ static bool gdbr_download_file(libgdbr_t *g, const char *remote, const char *loc return ok; } -RZ_API bool rz_io_gdb_download_file(RzIODesc *fd, const char *remote, const char *local) { - rz_return_val_if_fail(fd && remote && local, false); - if (!fd->plugin || strcmp(fd->plugin->name, "gdb")) { +static bool io_gdb_download_file(RzIO *io, RzIODesc *fd, const char *remote, const char *local) { + if (!fd || !fd->data) { return false; } return gdbr_download_file(fd->data, remote, local); @@ -492,6 +495,7 @@ RzIOPlugin rz_io_plugin_gdb = { .system = __system, .getpid = __getpid, .gettid = __gettid, + .download_file = io_gdb_download_file, .isdbg = true }; diff --git a/librz/main/rizin.c b/librz/main/rizin.c index 0644ca5a4d2..4d3b61a4767 100644 --- a/librz/main/rizin.c +++ b/librz/main/rizin.c @@ -31,7 +31,7 @@ static char *download_gdb_remote_file(RzIODesc *iod, const char *remote_path) { return NULL; } close(fd); - if (!rz_io_gdb_download_file(iod, remote_path, local_path)) { + if (!rz_io_desc_download_file(iod, remote_path, local_path)) { rz_file_rm(local_path); RZ_FREE(local_path); }