* [PATCH v2] gdbserver: widen the stat fields returned by the vFile packets
@ 2026-09-04 8:32 Abhay Kandpal
2026-09-16 13:22 ` Abhay Kandpal
0 siblings, 1 reply; 2+ messages in thread
From: Abhay Kandpal @ 2026-09-04 8:32 UTC (permalink / raw)
To: gdb-patches; +Cc: aburgess, eliz, cel, abhay.k, Abhay Kandpal
The struct sent by the vFile:fstat, vFile:stat and vFile:lstat packets
declares st_dev, st_ino, st_nlink, st_rdev and the three timestamps as
4-byte fields, while the corresponding struct stat members are 8 bytes
on a 64-bit host. The values are silently truncated.
This is not visible on a filesystem whose inode numbers stay below
2^32, but it is on a large one. On an 86 TB xfs volume,
gdb.server/fileio-packets.exp fails because the inode reported by
vFile:lstat does not match the one reported locally:
remote = {..., 'st_ino': 2161233200, ...}
local = {..., 'st_ino': 122420317488, ...}
The field widths cannot simply be changed, as that would alter the wire
layout and break communication between different versions of GDB and
gdbserver. Add a new 'stat64' qSupported feature instead. When both
sides report it, the vFile stat packets return struct fio_stat_wide, in
which the six too-narrow fields are 8 bytes; otherwise the existing
struct fio_stat is used, exactly as before.
Internally both GDB and gdbserver now work with the wide struct, and
the conversion happens at the wire boundary: gdbserver narrows the
result before sending it if the feature was not negotiated, and GDB
selects the unpacker by the length of the reply. Narrowing a value
which does not fit now produces a warning rather than truncating
silently.
Only the vFile packets are changed. The F-packet file-i/o protocol
uses the same struct, but writes it directly into inferior memory,
where the target reads it as the layout documented in the manual under
"struct stat"; that layout is left alone.
While here, drop a duplicate include of gdbsupport/fileio.h from
gdbserver/hostio.cc.
Tested on powerpc64le-linux; gdb.server/fileio-packets.exp now passes on
a filesystem with inode numbers above 2^32 and continues to pass on one
without, with no unexpected failures in the full testsuite.
gdb/
* NEWS: Mention the stat64 feature.
* doc/gdb.texinfo (General Query Packets): Document the stat64
feature.
(Host I/O Packets): Mention the wide reply format.
(struct stat64): New node.
* remote-fileio.c (remote_fileio_to_host_ulong8): New function.
(remote_fileio_wide_to_host_stat): New function.
* remote-fileio.h (remote_fileio_wide_to_host_stat): Declare.
* remote.c (PACKET_stat64_feature): New enum value.
(remote_features::stat64_feature): New method.
(remote_protocol_features): Add stat64.
(remote_target::remote_query_supported): Send stat64+.
(fileio_process_fstat_and_lstat_reply): Handle both formats.
(INIT_GDB_FILE): Add the stat64-feature packet command.
* testsuite/gdb.server/fileio-packets.py (decode_stat_reply):
Handle both the narrow and wide reply formats.
gdbserver/
* hostio.cc (handle_fstat, handle_stat, handle_lstat): Send the
wide struct when the feature is negotiated.
* server.cc (handle_query): Handle and report stat64.
* server.h (client_state::stat64_feature): New member.
gdbsupport/
* fileio.cc (host_to_fileio_stat_wide): New function.
(narrow_field, fileio_stat_wide_to_narrow): New functions.
* fileio.h (struct fio_stat_wide): New struct.
(bigendian_to_host): New function.
(host_to_fileio_stat_wide, fileio_stat_wide_to_narrow): Declare.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34567
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
This patch is reg tested.
gdb/NEWS | 13 +++
gdb/doc/gdb.texinfo | 50 ++++++++++
gdb/remote-fileio.c | 35 ++++++-
gdb/remote-fileio.h | 4 +
gdb/remote.c | 36 +++++--
gdb/testsuite/gdb.server/fileio-packets.py | 59 +++++++-----
gdbserver/hostio.cc | 106 +++++++++++++++------
gdbserver/server.cc | 4 +-
gdbserver/server.h | 5 +
gdbsupport/fileio.cc | 70 ++++++++++++++
gdbsupport/fileio.h | 54 ++++++++++-
11 files changed, 370 insertions(+), 66 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 222f38e3d52..7c1a09752a9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,19 @@
*** Changes since GDB 18
+* Changed remote packets
+
+stat64 in qSupported
+
+ The new stat64 feature within the qSupported packet allows GDB and
+ the stub to agree on a wider form of the stat structure returned by
+ the vFile:fstat, vFile:stat and vFile:lstat packets, in which st_dev,
+ st_ino, st_nlink, st_rdev and the three timestamps are 8 bytes rather
+ than 4. The wider form is used only when both sides report the
+ feature; otherwise the existing format is used and values which do
+ not fit are truncated as before. Note that the narrow form reads
+ timestamps as unsigned, so it overflows in 2106 rather than 2038.
+
*** Changes in GDB 18
* Support for the Common Trace Format (CTF) has been removed. GDB now
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 0030698dcee..355153212f0 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -46112,6 +46112,11 @@ inspected, if @samp{qIsAddressTagged} (@pxref{qIsAddressTagged}) packet
is not supported by the stub. Access to the @file{/proc/@var{pid}/smaps}
file is done via @samp{vFile} requests.
+@item stat64
+The remote stub supports the wider form of the stat structure returned
+by the @samp{vFile:fstat}, @samp{vFile:stat} and @samp{vFile:lstat}
+packets (@pxref{struct stat64}).
+
@item error-message
The remote stub supports replying with an error in a
@samp{E.@var{errtext}} (@xref{textual error reply}) format from the
@@ -47358,6 +47363,9 @@ On success the information is returned as a binary attachment
and the return value is the size of this attachment in bytes.
If an error occurs the return value is -1. The format of the
returned binary attachment is as described in @ref{struct stat}.
+If both @value{GDBN} and the stub report the @samp{stat64} feature in
+the @samp{qSupported} exchange, the wider format is used instead
+(@pxref{struct stat64}).
@item vFile:stat: @var{filename}
Get information about the file @var{filename} on the target as if from
@@ -47365,6 +47373,9 @@ a @samp{stat} call. On success the information is returned as a binary
attachment and the return value is the size of this attachment in
bytes. If an error occurs the return value is -1. The format of the
returned binary attachment is as described in @ref{struct stat}.
+If both @value{GDBN} and the stub report the @samp{stat64} feature in
+the @samp{qSupported} exchange, the wider format is used instead
+(@pxref{struct stat64}).
If @var{filename} is a symbolic link, then the information returned is
about the file the link refers to, this is inline with the @samp{stat}
@@ -47376,6 +47387,9 @@ an @samp{lstat} call. On success the information is returned as a
binary attachment and the return value is the size of this attachment
in bytes. If an error occurs the return value is -1. The format of
the returned binary attachment is as described in @ref{struct stat}.
+If both @value{GDBN} and the stub report the @samp{stat64} feature in
+the @samp{qSupported} exchange, the wider format is used instead
+(@pxref{struct stat64}).
This packet is identical to @samp{vFile:stat}, except if
@var{filename} is a symbolic link, then this packet returns
@@ -48570,6 +48584,7 @@ protocol.
* Pointer Values::
* Memory Transfer::
* struct stat::
+* struct stat64::
* struct timeval::
@end menu
@@ -48696,6 +48711,41 @@ Note that due to size differences between the host, target, and protocol
representations of @code{struct stat} members, these members could eventually
get truncated on the target.
+@node struct stat64
+@unnumberedsubsubsec struct stat64
+@cindex struct stat64, in file-i/o protocol
+
+When the @samp{stat64} feature has been negotiated, the buffer returned
+by the @samp{vFile:fstat}, @samp{vFile:stat} and @samp{vFile:lstat}
+packets uses the following representation instead:
+
+@smallexample
+struct stat64 @{
+ uint64_t st_dev; /* device */
+ uint64_t st_ino; /* inode */
+ mode_t st_mode; /* protection */
+ uint64_t st_nlink; /* number of hard links */
+ unsigned int st_uid; /* user ID of owner */
+ unsigned int st_gid; /* group ID of owner */
+ uint64_t st_rdev; /* device type (if inode device) */
+ uint64_t st_size; /* total size, in bytes */
+ uint64_t st_blksize; /* blocksize for filesystem I/O */
+ uint64_t st_blocks; /* number of blocks allocated */
+ uint64_t st_atime; /* time of last access */
+ uint64_t st_mtime; /* time of last modification */
+ uint64_t st_ctime; /* time of last change */
+@};
+@end smallexample
+
+This structure is of size 92 bytes. The fields have the same meanings
+as in the narrow form (@pxref{struct stat}), but @code{st_dev},
+@code{st_ino}, @code{st_nlink}, @code{st_rdev} and the three timestamps
+are wide enough to hold the values found on a 64-bit host without
+truncation.
+
+This representation is only used by the @samp{vFile} packets; the
+File-I/O protocol always uses the narrow form (@pxref{struct stat}).
+
@node struct timeval
@unnumberedsubsubsec struct timeval
@cindex struct timeval, in file-i/o protocol
diff --git a/gdb/remote-fileio.c b/gdb/remote-fileio.c
index a151161371d..76deb80d10a 100644
--- a/gdb/remote-fileio.c
+++ b/gdb/remote-fileio.c
@@ -1220,7 +1220,40 @@ remote_fileio_to_host_stat (struct fio_stat *fst, struct stat *st)
st->st_mtime = remote_fileio_to_host_time (fst->fst_mtime);
st->st_ctime = remote_fileio_to_host_time (fst->fst_ctime);
}
-\f
+
+/* Unpack an 8-byte protocol field. */
+
+static ULONGEST
+remote_fileio_to_host_ulong8 (const char *fnum)
+{
+ return extract_unsigned_integer ((const gdb_byte *) fnum, 8,
+ BFD_ENDIAN_BIG);
+}
+
+/* See remote-fileio.h. */
+
+void
+remote_fileio_wide_to_host_stat (struct fio_stat_wide *fst, struct stat *st)
+{
+ memset (st, 0, sizeof (struct stat));
+ st->st_dev = remote_fileio_to_host_ulong8 (fst->fst_dev);
+ st->st_ino = remote_fileio_to_host_ulong8 (fst->fst_ino);
+ st->st_mode = remote_fileio_to_host_mode (fst->fst_mode);
+ st->st_nlink = remote_fileio_to_host_ulong8 (fst->fst_nlink);
+ st->st_uid = remote_fileio_to_host_uint (fst->fst_uid);
+ st->st_gid = remote_fileio_to_host_uint (fst->fst_gid);
+ st->st_rdev = remote_fileio_to_host_ulong8 (fst->fst_rdev);
+ st->st_size = remote_fileio_to_host_ulong8 (fst->fst_size);
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ st->st_blksize = remote_fileio_to_host_ulong8 (fst->fst_blksize);
+#endif
+#if HAVE_STRUCT_STAT_ST_BLOCKS
+ st->st_blocks = remote_fileio_to_host_ulong8 (fst->fst_blocks);
+#endif
+ st->st_atime = remote_fileio_to_host_ulong8 (fst->fst_atime);
+ st->st_mtime = remote_fileio_to_host_ulong8 (fst->fst_mtime);
+ st->st_ctime = remote_fileio_to_host_ulong8 (fst->fst_ctime);
+}
static void
set_system_call_allowed (const char *args, int from_tty)
diff --git a/gdb/remote-fileio.h b/gdb/remote-fileio.h
index d01b3df92ca..95fb042fc9d 100644
--- a/gdb/remote-fileio.h
+++ b/gdb/remote-fileio.h
@@ -44,4 +44,8 @@ extern void initialize_remote_fileio (
extern void remote_fileio_to_host_stat (struct fio_stat *fst,
struct stat *st);
+/* Unpack a struct fio_stat_wide. */
+extern void remote_fileio_wide_to_host_stat (struct fio_stat_wide *fst,
+ struct stat *st);
+
#endif /* GDB_REMOTE_FILEIO_H */
diff --git a/gdb/remote.c b/gdb/remote.c
index 76cf6b5234f..03cb8a074b7 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -389,6 +389,8 @@ enum {
packets and the tag violation stop replies. */
PACKET_memory_tagging_feature,
+ PACKET_stat64_feature,
+
/* Support for the qIsAddressTagged packet. */
PACKET_qIsAddressTagged,
@@ -844,6 +846,9 @@ struct remote_features
bool remote_memory_tagging_p () const
{ return packet_support (PACKET_memory_tagging_feature) == PACKET_ENABLE; }
+ bool stat64_feature () const
+ { return packet_support (PACKET_stat64_feature) == PACKET_ENABLE; }
+
/* Returns true if there is support for sending vRun inferior arguments
as a single string. */
bool remote_vrun_single_arg_p () const
@@ -6199,6 +6204,8 @@ static const struct protocol_feature remote_protocol_features[] = {
{ "no-resumed", PACKET_DISABLE, remote_supported_packet, PACKET_no_resumed },
{ "memory-tagging", PACKET_DISABLE, remote_supported_packet,
PACKET_memory_tagging_feature },
+ { "stat64", PACKET_DISABLE, remote_supported_packet,
+ PACKET_stat64_feature },
{ "error-message", PACKET_ENABLE, remote_supported_packet,
PACKET_accept_error_message },
{ "binary-upload", PACKET_DISABLE, remote_supported_packet, PACKET_x },
@@ -6309,6 +6316,10 @@ remote_target::remote_query_supported ()
!= AUTO_BOOLEAN_FALSE)
remote_query_supported_append (&q, "QThreadOptions+");
+ if (m_features.packet_set_cmd_state (PACKET_stat64_feature)
+ != AUTO_BOOLEAN_FALSE)
+ remote_query_supported_append (&q, "stat64+");
+
if (m_features.packet_set_cmd_state (PACKET_no_resumed)
!= AUTO_BOOLEAN_FALSE)
remote_query_supported_append (&q, "no-resumed+");
@@ -13663,29 +13674,35 @@ remote_target::fileio_readlink (struct inferior *inf, const char *filename,
remote syscall reply. EXPECTED_LEN is the length returned from the
fstat or stat call, this the length of the returned data (in ATTACHMENT)
once it has been decoded. The fstat/stat result (from the ATTACHMENT
- data) is to be placed in ST. */
+ data) is to be placed in ST.
+ The reply holds either a struct fio_stat or, if the stat64 feature was
+ negotiated, the wider struct fio_stat_wide; the two are distinguished
+ by their length. */
static int
fileio_process_fstat_and_lstat_reply (const char *attachment,
int attachment_len,
int expected_len,
struct stat *st)
{
- struct fio_stat fst;
+ gdb_byte buf[sizeof (struct fio_stat_wide)];
int read_len
= remote_unescape_input ((gdb_byte *) attachment, attachment_len,
- (gdb_byte *) &fst, sizeof (fst));
+ buf, sizeof (buf));
if (read_len != expected_len)
error (_("vFile:fstat returned %d, but %d bytes."),
expected_len, read_len);
- if (read_len != sizeof (fst))
- error (_("vFile:fstat returned %d bytes, but expecting %d."),
- read_len, (int) sizeof (fst));
-
- remote_fileio_to_host_stat (&fst, st);
+ if (read_len == sizeof (struct fio_stat_wide))
+ remote_fileio_wide_to_host_stat ((struct fio_stat_wide *) buf, st);
+ else if (read_len == sizeof (struct fio_stat))
+ remote_fileio_to_host_stat ((struct fio_stat *) buf, st);
+ else
+ error (_("vFile:fstat returned %d bytes, but expecting %d or %d."),
+ read_len, (int) sizeof (struct fio_stat),
+ (int) sizeof (struct fio_stat_wide));
return 0;
}
@@ -17039,6 +17056,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
add_packet_config_cmd (PACKET_memory_tagging_feature,
"memory-tagging-feature", "memory-tagging-feature", 0);
+ add_packet_config_cmd (PACKET_stat64_feature,
+ "stat64-feature", "stat64-feature", 0);
+
add_packet_config_cmd (PACKET_qIsAddressTagged,
"qIsAddressTagged", "memory-tagging-address-check", 0);
diff --git a/gdb/testsuite/gdb.server/fileio-packets.py b/gdb/testsuite/gdb.server/fileio-packets.py
index 54ae56fa68a..2d06edcdbf7 100644
--- a/gdb/testsuite/gdb.server/fileio-packets.py
+++ b/gdb/testsuite/gdb.server/fileio-packets.py
@@ -57,33 +57,48 @@ def decode_stat_reply(buf, byteorder="big"):
buf = unescape_remote_data(buf)
- if (
- buf[0] != ord("F")
- or buf[1] != ord("4")
- or buf[2] != ord("0")
- or buf[3] != ord(";")
- or len(buf) != 68
- ):
+ if buf[0:4] == b"F40;" and len(buf) == 68:
+ wide = False
+ elif buf[0:4] == b"F5c;" and len(buf) == 96:
+ wide = True
+ else:
print(f"decode_stat_reply failed: {buf}\t(length = {len(buf)})")
return None
- # Discard the 'F40;' prefix. The rest is the 64 bytes of data to
- # be decoded.
+ # Discard the 'FNN;' prefix.
buf = buf[4:]
- st_dev = int.from_bytes(buf[0:4], byteorder=byteorder)
- st_ino = int.from_bytes(buf[4:8], byteorder=byteorder)
- st_mode = int.from_bytes(buf[8:12], byteorder=byteorder)
- st_nlink = int.from_bytes(buf[12:16], byteorder=byteorder)
- st_uid = int.from_bytes(buf[16:20], byteorder=byteorder)
- st_gid = int.from_bytes(buf[20:24], byteorder=byteorder)
- st_rdev = int.from_bytes(buf[24:28], byteorder=byteorder)
- st_size = int.from_bytes(buf[28:36], byteorder=byteorder)
- st_blksize = int.from_bytes(buf[36:44], byteorder=byteorder)
- st_blocks = int.from_bytes(buf[44:52], byteorder=byteorder)
- st_atime = int.from_bytes(buf[52:56], byteorder=byteorder)
- st_mtime = int.from_bytes(buf[56:60], byteorder=byteorder)
- st_ctime = int.from_bytes(buf[60:64], byteorder=byteorder)
+ def field(off, size):
+ return int.from_bytes(buf[off : off + size], byteorder=byteorder)
+
+ if wide:
+ st_dev = field(0, 8)
+ st_ino = field(8, 8)
+ st_mode = field(16, 4)
+ st_nlink = field(20, 8)
+ st_uid = field(28, 4)
+ st_gid = field(32, 4)
+ st_rdev = field(36, 8)
+ st_size = field(44, 8)
+ st_blksize = field(52, 8)
+ st_blocks = field(60, 8)
+ st_atime = field(68, 8)
+ st_mtime = field(76, 8)
+ st_ctime = field(84, 8)
+ else:
+ st_dev = field(0, 4)
+ st_ino = field(4, 4)
+ st_mode = field(8, 4)
+ st_nlink = field(12, 4)
+ st_uid = field(16, 4)
+ st_gid = field(20, 4)
+ st_rdev = field(24, 4)
+ st_size = field(28, 8)
+ st_blksize = field(36, 8)
+ st_blocks = field(44, 8)
+ st_atime = field(52, 4)
+ st_mtime = field(56, 4)
+ st_ctime = field(60, 4)
return {
"st_dev": st_dev,
diff --git a/gdbserver/hostio.cc b/gdbserver/hostio.cc
index b9ca27c2ff4..34def5d22da 100644
--- a/gdbserver/hostio.cc
+++ b/gdbserver/hostio.cc
@@ -26,7 +26,6 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include "gdbsupport/fileio.h"
struct fd_list
{
@@ -465,7 +464,7 @@ handle_fstat (char *own_buf, int *new_packet_len)
int fd, bytes_sent;
char *p;
struct stat st;
- struct fio_stat fst;
+ client_state &cs = get_client_state ();
p = own_buf + strlen ("vFile:fstat:");
@@ -483,16 +482,31 @@ handle_fstat (char *own_buf, int *new_packet_len)
return;
}
- host_to_fileio_stat (&st, &fst);
-
- bytes_sent = hostio_reply_with_data (own_buf,
- (char *) &fst, sizeof (fst),
- new_packet_len);
+ struct fio_stat_wide wide;
+ host_to_fileio_stat_wide (&st, &wide);
- /* If the response does not fit into a single packet, do not attempt
- to return a partial response, but simply fail. */
- if (bytes_sent < sizeof (fst))
- write_enn (own_buf);
+ /* Send the wide form if GDB understands it, otherwise narrow the
+ result first. */
+ if (cs.stat64_feature)
+ {
+ bytes_sent = hostio_reply_with_data (own_buf, (char *) &wide,
+ sizeof (wide), new_packet_len);
+ /* If the response does not fit into a single packet, do not attempt
+ to return a partial response, but simply fail. */
+ if (bytes_sent < sizeof (wide))
+ write_enn (own_buf);
+ }
+ else
+ {
+ struct fio_stat narrow;
+ fileio_stat_wide_to_narrow (&wide, &narrow);
+ bytes_sent = hostio_reply_with_data (own_buf, (char *) &narrow,
+ sizeof (narrow), new_packet_len);
+ /* If the response does not fit into a single packet, do not attempt
+ to return a partial response, but simply fail. */
+ if (bytes_sent < sizeof (narrow))
+ write_enn (own_buf);
+ }
}
static void
@@ -501,8 +515,8 @@ handle_stat (char *own_buf, int *new_packet_len)
int bytes_sent;
char *p;
struct stat st;
- struct fio_stat fst;
char filename[HOSTIO_PATH_MAX];
+ client_state &cs = get_client_state ();
p = own_buf + strlen ("vFile:stat:");
@@ -519,16 +533,31 @@ handle_stat (char *own_buf, int *new_packet_len)
return;
}
- host_to_fileio_stat (&st, &fst);
-
- bytes_sent = hostio_reply_with_data (own_buf,
- (char *) &fst, sizeof (fst),
- new_packet_len);
+ struct fio_stat_wide wide;
+ host_to_fileio_stat_wide (&st, &wide);
- /* If the response does not fit into a single packet, do not attempt
- to return a partial response, but simply fail. */
- if (bytes_sent < sizeof (fst))
- write_enn (own_buf);
+ /* Send the wide form if GDB understands it, otherwise narrow the
+ result first. */
+ if (cs.stat64_feature)
+ {
+ bytes_sent = hostio_reply_with_data (own_buf, (char *) &wide,
+ sizeof (wide), new_packet_len);
+ /* If the response does not fit into a single packet, do not attempt
+ to return a partial response, but simply fail. */
+ if (bytes_sent < sizeof (wide))
+ write_enn (own_buf);
+ }
+ else
+ {
+ struct fio_stat narrow;
+ fileio_stat_wide_to_narrow (&wide, &narrow);
+ bytes_sent = hostio_reply_with_data (own_buf, (char *) &narrow,
+ sizeof (narrow), new_packet_len);
+ /* If the response does not fit into a single packet, do not attempt
+ to return a partial response, but simply fail. */
+ if (bytes_sent < sizeof (narrow))
+ write_enn (own_buf);
+ }
}
static void
@@ -537,8 +566,8 @@ handle_lstat (char *own_buf, int *new_packet_len)
int ret, bytes_sent;
char *p;
struct stat st;
- struct fio_stat fst;
char filename[HOSTIO_PATH_MAX];
+ client_state &cs = get_client_state ();
p = own_buf + strlen ("vFile:lstat:");
@@ -560,16 +589,31 @@ handle_lstat (char *own_buf, int *new_packet_len)
return;
}
- host_to_fileio_stat (&st, &fst);
-
- bytes_sent = hostio_reply_with_data (own_buf,
- (char *) &fst, sizeof (fst),
- new_packet_len);
+ struct fio_stat_wide wide;
+ host_to_fileio_stat_wide (&st, &wide);
- /* If the response does not fit into a single packet, do not attempt
- to return a partial response, but simply fail. */
- if (bytes_sent < sizeof (fst))
- write_enn (own_buf);
+ /* Send the wide form if GDB understands it, otherwise narrow the
+ result first. */
+ if (cs.stat64_feature)
+ {
+ bytes_sent = hostio_reply_with_data (own_buf, (char *) &wide,
+ sizeof (wide), new_packet_len);
+ /* If the response does not fit into a single packet, do not attempt
+ to return a partial response, but simply fail. */
+ if (bytes_sent < sizeof (wide))
+ write_enn (own_buf);
+ }
+ else
+ {
+ struct fio_stat narrow;
+ fileio_stat_wide_to_narrow (&wide, &narrow);
+ bytes_sent = hostio_reply_with_data (own_buf, (char *) &narrow,
+ sizeof (narrow), new_packet_len);
+ /* If the response does not fit into a single packet, do not attempt
+ to return a partial response, but simply fail. */
+ if (bytes_sent < sizeof (narrow))
+ write_enn (own_buf);
+ }
}
static void
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index a208e24d09f..dfa24617847 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -2746,6 +2746,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
cs.single_inferior_argument = true;
else if (feature == "multi-wp-addr+")
cs.multiple_wp_addr_feature = true;
+ else if (feature == "stat64+")
+ cs.stat64_feature = true;
else
{
/* Move the unknown features all together. */
@@ -2762,7 +2764,7 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
"PacketSize=%x;QPassSignals+;QProgramSignals+;"
"QStartupWithShell+;QEnvironmentHexEncoded+;"
"QEnvironmentReset+;QEnvironmentUnset+;"
- "QSetWorkingDir+;binary-upload+",
+ "QSetWorkingDir+;binary-upload+;stat64+",
PBUFSIZ - 1);
if (target_supports_catch_syscall ())
diff --git a/gdbserver/server.h b/gdbserver/server.h
index bbba3650de7..cf811f228fa 100644
--- a/gdbserver/server.h
+++ b/gdbserver/server.h
@@ -192,6 +192,11 @@ struct client_state
/* If true, memory tagging features are supported. */
bool memory_tagging_feature = false;
+ /* True if GDB supports the wide form of struct stat in the vFile
+ fstat, stat and lstat replies. When false, gdbserver narrows the
+ result to struct fio_stat before sending it. */
+ bool stat64_feature = false;
+
/* If true then E.errtext style errors are supported everywhere,
including for the qRcmd and m packet. When false E.errtext errors
are not supported with qRcmd and m packets, but are still supported
diff --git a/gdbsupport/fileio.cc b/gdbsupport/fileio.cc
index 6d2ffb14544..bab2c4c8efe 100644
--- a/gdbsupport/fileio.cc
+++ b/gdbsupport/fileio.cc
@@ -20,6 +20,7 @@
#include "fileio.h"
#include <sys/stat.h>
#include <fcntl.h>
+#include "gdbsupport/errors.h"
/* See fileio.h. */
@@ -305,3 +306,72 @@ host_to_fileio_stat (struct stat *st, struct fio_stat *fst)
host_to_fileio_time (st->st_mtime, fst->fst_mtime);
host_to_fileio_time (st->st_ctime, fst->fst_ctime);
}
+
+/* See fileio.h. */
+
+void
+host_to_fileio_stat_wide (struct stat *st, struct fio_stat_wide *fst)
+{
+ LONGEST blksize;
+
+ host_to_bigendian ((LONGEST) st->st_dev, (char *) fst->fst_dev, 8);
+ host_to_bigendian ((LONGEST) st->st_ino, (char *) fst->fst_ino, 8);
+ host_to_fileio_mode (st->st_mode, fst->fst_mode);
+ host_to_bigendian ((LONGEST) st->st_nlink, (char *) fst->fst_nlink, 8);
+ host_to_fileio_uint ((long) st->st_uid, fst->fst_uid);
+ host_to_fileio_uint ((long) st->st_gid, fst->fst_gid);
+ host_to_bigendian ((LONGEST) st->st_rdev, (char *) fst->fst_rdev, 8);
+ host_to_bigendian ((LONGEST) st->st_size, (char *) fst->fst_size, 8);
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ blksize = st->st_blksize;
+#else
+ blksize = 512;
+#endif
+ host_to_bigendian (blksize, (char *) fst->fst_blksize, 8);
+#if HAVE_STRUCT_STAT_ST_BLOCKS
+ host_to_bigendian ((LONGEST) st->st_blocks, (char *) fst->fst_blocks, 8);
+#else
+ host_to_bigendian (((LONGEST) st->st_size + blksize - 1) / blksize,
+ (char *) fst->fst_blocks, 8);
+#endif
+ host_to_bigendian ((LONGEST) st->st_atime, (char *) fst->fst_atime, 8);
+ host_to_bigendian ((LONGEST) st->st_mtime, (char *) fst->fst_mtime, 8);
+ host_to_bigendian ((LONGEST) st->st_ctime, (char *) fst->fst_ctime, 8);
+}
+
+/* Copy the 8-byte value in FROM into the 4-byte field TO, warning if
+ the value does not fit. WHAT names the field, for the warning. */
+
+static void
+narrow_field (const char *from, char *to, const char *what)
+{
+ ULONGEST num = bigendian_to_host (from, 8);
+
+ if (num != (ULONGEST) (uint32_t) num)
+ warning (_("file-i/o: %s value %s does not fit in 32 bits; "
+ "the value seen by the peer will be truncated"),
+ what, pulongest (num));
+
+ host_to_bigendian ((LONGEST) num, to, 4);
+}
+
+/* See fileio.h. */
+
+void
+fileio_stat_wide_to_narrow (const struct fio_stat_wide *wide,
+ struct fio_stat *narrow)
+{
+ narrow_field (wide->fst_dev, narrow->fst_dev, "st_dev");
+ narrow_field (wide->fst_ino, narrow->fst_ino, "st_ino");
+ memcpy (narrow->fst_mode, wide->fst_mode, 4);
+ narrow_field (wide->fst_nlink, narrow->fst_nlink, "st_nlink");
+ memcpy (narrow->fst_uid, wide->fst_uid, 4);
+ memcpy (narrow->fst_gid, wide->fst_gid, 4);
+ narrow_field (wide->fst_rdev, narrow->fst_rdev, "st_rdev");
+ memcpy (narrow->fst_size, wide->fst_size, 8);
+ memcpy (narrow->fst_blksize, wide->fst_blksize, 8);
+ memcpy (narrow->fst_blocks, wide->fst_blocks, 8);
+ narrow_field (wide->fst_atime, narrow->fst_atime, "st_atime");
+ narrow_field (wide->fst_mtime, narrow->fst_mtime, "st_mtime");
+ narrow_field (wide->fst_ctime, narrow->fst_ctime, "st_ctime");
+}
diff --git a/gdbsupport/fileio.h b/gdbsupport/fileio.h
index c2592cf8c15..fa5de54dd40 100644
--- a/gdbsupport/fileio.h
+++ b/gdbsupport/fileio.h
@@ -120,9 +120,11 @@ using fio_time_t = char[FIO_TIME_LEN];
using fio_long_t = char[FIO_LONG_LEN];
using fio_ulong_t = char[FIO_ULONG_LEN];
-/* Struct stat as used in protocol. For complete independence
- of host/target systems, it's defined as an array with offsets
- to the members. */
+/* Struct stat as used in the protocol. This is the format used by the
+ F-packet file-i/o protocol, and by the vFile stat packets when the
+ stat64 feature has not been negotiated. Several fields are too narrow
+ to hold the corresponding struct stat member on a 64-bit host; see
+ struct fio_stat_wide. */
struct fio_stat
{
@@ -141,6 +143,29 @@ struct fio_stat
fio_time_t fst_ctime;
};
+/* Struct stat as used in the wide form of the protocol. The fields
+ which cannot hold the corresponding struct stat member on a 64-bit
+ host are widened to 8 bytes. This layout is only used by the vFile
+ stat packets, and only when the stat64 feature has been negotiated
+ with the peer. */
+
+struct fio_stat_wide
+{
+ fio_ulong_t fst_dev;
+ fio_ulong_t fst_ino;
+ fio_mode_t fst_mode;
+ fio_ulong_t fst_nlink;
+ fio_uint_t fst_uid;
+ fio_uint_t fst_gid;
+ fio_ulong_t fst_rdev;
+ fio_ulong_t fst_size;
+ fio_ulong_t fst_blksize;
+ fio_ulong_t fst_blocks;
+ fio_ulong_t fst_atime;
+ fio_ulong_t fst_mtime;
+ fio_ulong_t fst_ctime;
+};
+
struct fio_timeval
{
fio_time_t ftv_sec;
@@ -179,6 +204,20 @@ host_to_bigendian (LONGEST num, char *buf, int bytes)
buf[i] = (num >> (8 * (bytes - i - 1))) & 0xff;
}
+/* Unpack an integer of BYTES bytes from BUF, which holds it in
+ big-endian, protocol format. */
+
+static inline ULONGEST
+bigendian_to_host (const char *buf, int bytes)
+{
+ ULONGEST num = 0;
+ int i;
+
+ for (i = 0; i < bytes; ++i)
+ num = (num << 8) | (unsigned char) buf[i];
+ return num;
+}
+
/* Pack a host-format integer into an fio_uint_t. */
static inline void
@@ -199,4 +238,13 @@ host_to_fileio_time (time_t num, fio_time_t fnum)
extern void host_to_fileio_stat (struct stat *st, struct fio_stat *fst);
+/* Pack a host-format struct stat into a struct fio_stat_wide. */
+extern void host_to_fileio_stat_wide (struct stat *st,
+ struct fio_stat_wide *fst);
+
+/* Narrow a struct fio_stat_wide into a struct fio_stat, warning if any
+ value does not fit. */
+extern void fileio_stat_wide_to_narrow (const struct fio_stat_wide *wide,
+ struct fio_stat *narrow);
+
#endif /* GDBSUPPORT_FILEIO_H */
--
2.52.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [PATCH v2] gdbserver: widen the stat fields returned by the vFile packets
2026-09-04 8:32 [PATCH v2] gdbserver: widen the stat fields returned by the vFile packets Abhay Kandpal
@ 2026-09-16 13:22 ` Abhay Kandpal
0 siblings, 0 replies; 2+ messages in thread
From: Abhay Kandpal @ 2026-09-16 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: aburgess, eliz, cel, abhay.k
[-- Attachment #1: Type: text/plain, Size: 32572 bytes --]
Hi Andrew,
Ping. Let me know if the vFile-only scope looks right to you.
Thanks,
Abhay
On 04/09/26 14:02, Abhay Kandpal wrote:
> The struct sent by the vFile:fstat, vFile:stat and vFile:lstat packets
> declares st_dev, st_ino, st_nlink, st_rdev and the three timestamps as
> 4-byte fields, while the corresponding struct stat members are 8 bytes
> on a 64-bit host. The values are silently truncated.
>
> This is not visible on a filesystem whose inode numbers stay below
> 2^32, but it is on a large one. On an 86 TB xfs volume,
> gdb.server/fileio-packets.exp fails because the inode reported by
> vFile:lstat does not match the one reported locally:
>
> remote = {..., 'st_ino': 2161233200, ...}
> local = {..., 'st_ino': 122420317488, ...}
>
> The field widths cannot simply be changed, as that would alter the wire
> layout and break communication between different versions of GDB and
> gdbserver. Add a new 'stat64' qSupported feature instead. When both
> sides report it, the vFile stat packets return struct fio_stat_wide, in
> which the six too-narrow fields are 8 bytes; otherwise the existing
> struct fio_stat is used, exactly as before.
>
> Internally both GDB and gdbserver now work with the wide struct, and
> the conversion happens at the wire boundary: gdbserver narrows the
> result before sending it if the feature was not negotiated, and GDB
> selects the unpacker by the length of the reply. Narrowing a value
> which does not fit now produces a warning rather than truncating
> silently.
>
> Only the vFile packets are changed. The F-packet file-i/o protocol
> uses the same struct, but writes it directly into inferior memory,
> where the target reads it as the layout documented in the manual under
> "struct stat"; that layout is left alone.
>
> While here, drop a duplicate include of gdbsupport/fileio.h from
> gdbserver/hostio.cc.
>
> Tested on powerpc64le-linux; gdb.server/fileio-packets.exp now passes on
> a filesystem with inode numbers above 2^32 and continues to pass on one
> without, with no unexpected failures in the full testsuite.
>
> gdb/
> * NEWS: Mention the stat64 feature.
> * doc/gdb.texinfo (General Query Packets): Document the stat64
> feature.
> (Host I/O Packets): Mention the wide reply format.
> (struct stat64): New node.
> * remote-fileio.c (remote_fileio_to_host_ulong8): New function.
> (remote_fileio_wide_to_host_stat): New function.
> * remote-fileio.h (remote_fileio_wide_to_host_stat): Declare.
> * remote.c (PACKET_stat64_feature): New enum value.
> (remote_features::stat64_feature): New method.
> (remote_protocol_features): Add stat64.
> (remote_target::remote_query_supported): Send stat64+.
> (fileio_process_fstat_and_lstat_reply): Handle both formats.
> (INIT_GDB_FILE): Add the stat64-feature packet command.
> * testsuite/gdb.server/fileio-packets.py (decode_stat_reply):
> Handle both the narrow and wide reply formats.
>
> gdbserver/
> * hostio.cc (handle_fstat, handle_stat, handle_lstat): Send the
> wide struct when the feature is negotiated.
> * server.cc (handle_query): Handle and report stat64.
> * server.h (client_state::stat64_feature): New member.
>
> gdbsupport/
> * fileio.cc (host_to_fileio_stat_wide): New function.
> (narrow_field, fileio_stat_wide_to_narrow): New functions.
> * fileio.h (struct fio_stat_wide): New struct.
> (bigendian_to_host): New function.
> (host_to_fileio_stat_wide, fileio_stat_wide_to_narrow): Declare.
>
> Bug:https://sourceware.org/bugzilla/show_bug.cgi?id=34567
> Reviewed-By: Eli Zaretskii<eliz@gnu.org>
> ---
> This patch is reg tested.
>
> gdb/NEWS | 13 +++
> gdb/doc/gdb.texinfo | 50 ++++++++++
> gdb/remote-fileio.c | 35 ++++++-
> gdb/remote-fileio.h | 4 +
> gdb/remote.c | 36 +++++--
> gdb/testsuite/gdb.server/fileio-packets.py | 59 +++++++-----
> gdbserver/hostio.cc | 106 +++++++++++++++------
> gdbserver/server.cc | 4 +-
> gdbserver/server.h | 5 +
> gdbsupport/fileio.cc | 70 ++++++++++++++
> gdbsupport/fileio.h | 54 ++++++++++-
> 11 files changed, 370 insertions(+), 66 deletions(-)
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 222f38e3d52..7c1a09752a9 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,19 @@
>
> *** Changes since GDB 18
>
> +* Changed remote packets
> +
> +stat64 in qSupported
> +
> + The new stat64 feature within the qSupported packet allows GDB and
> + the stub to agree on a wider form of the stat structure returned by
> + the vFile:fstat, vFile:stat and vFile:lstat packets, in which st_dev,
> + st_ino, st_nlink, st_rdev and the three timestamps are 8 bytes rather
> + than 4. The wider form is used only when both sides report the
> + feature; otherwise the existing format is used and values which do
> + not fit are truncated as before. Note that the narrow form reads
> + timestamps as unsigned, so it overflows in 2106 rather than 2038.
> +
> *** Changes in GDB 18
>
> * Support for the Common Trace Format (CTF) has been removed. GDB now
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 0030698dcee..355153212f0 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -46112,6 +46112,11 @@ inspected, if @samp{qIsAddressTagged} (@pxref{qIsAddressTagged}) packet
> is not supported by the stub. Access to the @file{/proc/@var{pid}/smaps}
> file is done via @samp{vFile} requests.
>
> +@item stat64
> +The remote stub supports the wider form of the stat structure returned
> +by the @samp{vFile:fstat}, @samp{vFile:stat} and @samp{vFile:lstat}
> +packets (@pxref{struct stat64}).
> +
> @item error-message
> The remote stub supports replying with an error in a
> @samp{E.@var{errtext}} (@xref{textual error reply}) format from the
> @@ -47358,6 +47363,9 @@ On success the information is returned as a binary attachment
> and the return value is the size of this attachment in bytes.
> If an error occurs the return value is -1. The format of the
> returned binary attachment is as described in @ref{struct stat}.
> +If both @value{GDBN} and the stub report the @samp{stat64} feature in
> +the @samp{qSupported} exchange, the wider format is used instead
> +(@pxref{struct stat64}).
>
> @item vFile:stat: @var{filename}
> Get information about the file @var{filename} on the target as if from
> @@ -47365,6 +47373,9 @@ a @samp{stat} call. On success the information is returned as a binary
> attachment and the return value is the size of this attachment in
> bytes. If an error occurs the return value is -1. The format of the
> returned binary attachment is as described in @ref{struct stat}.
> +If both @value{GDBN} and the stub report the @samp{stat64} feature in
> +the @samp{qSupported} exchange, the wider format is used instead
> +(@pxref{struct stat64}).
>
> If @var{filename} is a symbolic link, then the information returned is
> about the file the link refers to, this is inline with the @samp{stat}
> @@ -47376,6 +47387,9 @@ an @samp{lstat} call. On success the information is returned as a
> binary attachment and the return value is the size of this attachment
> in bytes. If an error occurs the return value is -1. The format of
> the returned binary attachment is as described in @ref{struct stat}.
> +If both @value{GDBN} and the stub report the @samp{stat64} feature in
> +the @samp{qSupported} exchange, the wider format is used instead
> +(@pxref{struct stat64}).
>
> This packet is identical to @samp{vFile:stat}, except if
> @var{filename} is a symbolic link, then this packet returns
> @@ -48570,6 +48584,7 @@ protocol.
> * Pointer Values::
> * Memory Transfer::
> * struct stat::
> +* struct stat64::
> * struct timeval::
> @end menu
>
> @@ -48696,6 +48711,41 @@ Note that due to size differences between the host, target, and protocol
> representations of @code{struct stat} members, these members could eventually
> get truncated on the target.
>
> +@node struct stat64
> +@unnumberedsubsubsec struct stat64
> +@cindex struct stat64, in file-i/o protocol
> +
> +When the @samp{stat64} feature has been negotiated, the buffer returned
> +by the @samp{vFile:fstat}, @samp{vFile:stat} and @samp{vFile:lstat}
> +packets uses the following representation instead:
> +
> +@smallexample
> +struct stat64 @{
> + uint64_t st_dev; /* device */
> + uint64_t st_ino; /* inode */
> + mode_t st_mode; /* protection */
> + uint64_t st_nlink; /* number of hard links */
> + unsigned int st_uid; /* user ID of owner */
> + unsigned int st_gid; /* group ID of owner */
> + uint64_t st_rdev; /* device type (if inode device) */
> + uint64_t st_size; /* total size, in bytes */
> + uint64_t st_blksize; /* blocksize for filesystem I/O */
> + uint64_t st_blocks; /* number of blocks allocated */
> + uint64_t st_atime; /* time of last access */
> + uint64_t st_mtime; /* time of last modification */
> + uint64_t st_ctime; /* time of last change */
> +@};
> +@end smallexample
> +
> +This structure is of size 92 bytes. The fields have the same meanings
> +as in the narrow form (@pxref{struct stat}), but @code{st_dev},
> +@code{st_ino}, @code{st_nlink}, @code{st_rdev} and the three timestamps
> +are wide enough to hold the values found on a 64-bit host without
> +truncation.
> +
> +This representation is only used by the @samp{vFile} packets; the
> +File-I/O protocol always uses the narrow form (@pxref{struct stat}).
> +
> @node struct timeval
> @unnumberedsubsubsec struct timeval
> @cindex struct timeval, in file-i/o protocol
> diff --git a/gdb/remote-fileio.c b/gdb/remote-fileio.c
> index a151161371d..76deb80d10a 100644
> --- a/gdb/remote-fileio.c
> +++ b/gdb/remote-fileio.c
> @@ -1220,7 +1220,40 @@ remote_fileio_to_host_stat (struct fio_stat *fst, struct stat *st)
> st->st_mtime = remote_fileio_to_host_time (fst->fst_mtime);
> st->st_ctime = remote_fileio_to_host_time (fst->fst_ctime);
> }
> -\f
> +
> +/* Unpack an 8-byte protocol field. */
> +
> +static ULONGEST
> +remote_fileio_to_host_ulong8 (const char *fnum)
> +{
> + return extract_unsigned_integer ((const gdb_byte *) fnum, 8,
> + BFD_ENDIAN_BIG);
> +}
> +
> +/* See remote-fileio.h. */
> +
> +void
> +remote_fileio_wide_to_host_stat (struct fio_stat_wide *fst, struct stat *st)
> +{
> + memset (st, 0, sizeof (struct stat));
> + st->st_dev = remote_fileio_to_host_ulong8 (fst->fst_dev);
> + st->st_ino = remote_fileio_to_host_ulong8 (fst->fst_ino);
> + st->st_mode = remote_fileio_to_host_mode (fst->fst_mode);
> + st->st_nlink = remote_fileio_to_host_ulong8 (fst->fst_nlink);
> + st->st_uid = remote_fileio_to_host_uint (fst->fst_uid);
> + st->st_gid = remote_fileio_to_host_uint (fst->fst_gid);
> + st->st_rdev = remote_fileio_to_host_ulong8 (fst->fst_rdev);
> + st->st_size = remote_fileio_to_host_ulong8 (fst->fst_size);
> +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
> + st->st_blksize = remote_fileio_to_host_ulong8 (fst->fst_blksize);
> +#endif
> +#if HAVE_STRUCT_STAT_ST_BLOCKS
> + st->st_blocks = remote_fileio_to_host_ulong8 (fst->fst_blocks);
> +#endif
> + st->st_atime = remote_fileio_to_host_ulong8 (fst->fst_atime);
> + st->st_mtime = remote_fileio_to_host_ulong8 (fst->fst_mtime);
> + st->st_ctime = remote_fileio_to_host_ulong8 (fst->fst_ctime);
> +}
>
> static void
> set_system_call_allowed (const char *args, int from_tty)
> diff --git a/gdb/remote-fileio.h b/gdb/remote-fileio.h
> index d01b3df92ca..95fb042fc9d 100644
> --- a/gdb/remote-fileio.h
> +++ b/gdb/remote-fileio.h
> @@ -44,4 +44,8 @@ extern void initialize_remote_fileio (
> extern void remote_fileio_to_host_stat (struct fio_stat *fst,
> struct stat *st);
>
> +/* Unpack a struct fio_stat_wide. */
> +extern void remote_fileio_wide_to_host_stat (struct fio_stat_wide *fst,
> + struct stat *st);
> +
> #endif /* GDB_REMOTE_FILEIO_H */
> diff --git a/gdb/remote.c b/gdb/remote.c
> index 76cf6b5234f..03cb8a074b7 100644
> --- a/gdb/remote.c
> +++ b/gdb/remote.c
> @@ -389,6 +389,8 @@ enum {
> packets and the tag violation stop replies. */
> PACKET_memory_tagging_feature,
>
> + PACKET_stat64_feature,
> +
> /* Support for the qIsAddressTagged packet. */
> PACKET_qIsAddressTagged,
>
> @@ -844,6 +846,9 @@ struct remote_features
> bool remote_memory_tagging_p () const
> { return packet_support (PACKET_memory_tagging_feature) == PACKET_ENABLE; }
>
> + bool stat64_feature () const
> + { return packet_support (PACKET_stat64_feature) == PACKET_ENABLE; }
> +
> /* Returns true if there is support for sending vRun inferior arguments
> as a single string. */
> bool remote_vrun_single_arg_p () const
> @@ -6199,6 +6204,8 @@ static const struct protocol_feature remote_protocol_features[] = {
> { "no-resumed", PACKET_DISABLE, remote_supported_packet, PACKET_no_resumed },
> { "memory-tagging", PACKET_DISABLE, remote_supported_packet,
> PACKET_memory_tagging_feature },
> + { "stat64", PACKET_DISABLE, remote_supported_packet,
> + PACKET_stat64_feature },
> { "error-message", PACKET_ENABLE, remote_supported_packet,
> PACKET_accept_error_message },
> { "binary-upload", PACKET_DISABLE, remote_supported_packet, PACKET_x },
> @@ -6309,6 +6316,10 @@ remote_target::remote_query_supported ()
> != AUTO_BOOLEAN_FALSE)
> remote_query_supported_append (&q, "QThreadOptions+");
>
> + if (m_features.packet_set_cmd_state (PACKET_stat64_feature)
> + != AUTO_BOOLEAN_FALSE)
> + remote_query_supported_append (&q, "stat64+");
> +
> if (m_features.packet_set_cmd_state (PACKET_no_resumed)
> != AUTO_BOOLEAN_FALSE)
> remote_query_supported_append (&q, "no-resumed+"); @@ -13663,29 +13674,35 @@ remote_target::fileio_readlink (struct
> inferior *inf, const char *filename, remote syscall reply.
> EXPECTED_LEN is the length returned from the fstat or stat call, this
> the length of the returned data (in ATTACHMENT) once it has been
> decoded. The fstat/stat result (from the ATTACHMENT - data) is to be
> placed in ST. */ + data) is to be placed in ST. + The reply holds
> either a struct fio_stat or, if the stat64 feature was + negotiated,
> the wider struct fio_stat_wide; the two are distinguished + by their
> length. */ static int fileio_process_fstat_and_lstat_reply (const char
> *attachment, int attachment_len, int expected_len, struct stat *st) {
> - struct fio_stat fst; + gdb_byte buf[sizeof (struct fio_stat_wide)];
> int read_len = remote_unescape_input ((gdb_byte *) attachment,
> attachment_len, - (gdb_byte *) &fst, sizeof (fst)); + buf, sizeof
> (buf)); if (read_len != expected_len) error (_("vFile:fstat returned %d, but %d bytes."),
> expected_len, read_len);
>
> - if (read_len != sizeof (fst))
> - error (_("vFile:fstat returned %d bytes, but expecting %d."),
> - read_len, (int) sizeof (fst));
> -
> - remote_fileio_to_host_stat (&fst, st);
> + if (read_len == sizeof (struct fio_stat_wide))
> + remote_fileio_wide_to_host_stat ((struct fio_stat_wide *) buf, st);
> + else if (read_len == sizeof (struct fio_stat))
> + remote_fileio_to_host_stat ((struct fio_stat *) buf, st);
> + else
> + error (_("vFile:fstat returned %d bytes, but expecting %d or %d."), + read_len, (int) sizeof (struct fio_stat), + (int) sizeof (struct
> fio_stat_wide)); return 0; } @@ -17039,6 +17056,9 @@ Show the maximum
> size of the address (in bits) in a memory packet."), NULL,
> add_packet_config_cmd (PACKET_memory_tagging_feature,
> "memory-tagging-feature", "memory-tagging-feature", 0);
>
> + add_packet_config_cmd (PACKET_stat64_feature,
> + "stat64-feature", "stat64-feature", 0);
> +
> add_packet_config_cmd (PACKET_qIsAddressTagged,
> "qIsAddressTagged", "memory-tagging-address-check", 0);
>
> diff --git a/gdb/testsuite/gdb.server/fileio-packets.py b/gdb/testsuite/gdb.server/fileio-packets.py
> index 54ae56fa68a..2d06edcdbf7 100644
> --- a/gdb/testsuite/gdb.server/fileio-packets.py
> +++ b/gdb/testsuite/gdb.server/fileio-packets.py
> @@ -57,33 +57,48 @@ def decode_stat_reply(buf, byteorder="big"):
>
> buf = unescape_remote_data(buf)
>
> - if (
> - buf[0] != ord("F")
> - or buf[1] != ord("4")
> - or buf[2] != ord("0")
> - or buf[3] != ord(";")
> - or len(buf) != 68
> - ):
> + if buf[0:4] == b"F40;" and len(buf) == 68:
> + wide = False
> + elif buf[0:4] == b"F5c;" and len(buf) == 96:
> + wide = True
> + else:
> print(f"decode_stat_reply failed: {buf}\t(length = {len(buf)})")
> return None
>
> - # Discard the 'F40;' prefix. The rest is the 64 bytes of data to
> - # be decoded.
> + # Discard the 'FNN;' prefix.
> buf = buf[4:]
>
> - st_dev = int.from_bytes(buf[0:4], byteorder=byteorder)
> - st_ino = int.from_bytes(buf[4:8], byteorder=byteorder)
> - st_mode = int.from_bytes(buf[8:12], byteorder=byteorder)
> - st_nlink = int.from_bytes(buf[12:16], byteorder=byteorder)
> - st_uid = int.from_bytes(buf[16:20], byteorder=byteorder)
> - st_gid = int.from_bytes(buf[20:24], byteorder=byteorder)
> - st_rdev = int.from_bytes(buf[24:28], byteorder=byteorder)
> - st_size = int.from_bytes(buf[28:36], byteorder=byteorder)
> - st_blksize = int.from_bytes(buf[36:44], byteorder=byteorder)
> - st_blocks = int.from_bytes(buf[44:52], byteorder=byteorder)
> - st_atime = int.from_bytes(buf[52:56], byteorder=byteorder)
> - st_mtime = int.from_bytes(buf[56:60], byteorder=byteorder)
> - st_ctime = int.from_bytes(buf[60:64], byteorder=byteorder)
> + def field(off, size):
> + return int.from_bytes(buf[off : off + size], byteorder=byteorder)
> +
> + if wide:
> + st_dev = field(0, 8)
> + st_ino = field(8, 8)
> + st_mode = field(16, 4)
> + st_nlink = field(20, 8)
> + st_uid = field(28, 4)
> + st_gid = field(32, 4)
> + st_rdev = field(36, 8)
> + st_size = field(44, 8)
> + st_blksize = field(52, 8)
> + st_blocks = field(60, 8)
> + st_atime = field(68, 8)
> + st_mtime = field(76, 8)
> + st_ctime = field(84, 8)
> + else:
> + st_dev = field(0, 4)
> + st_ino = field(4, 4)
> + st_mode = field(8, 4)
> + st_nlink = field(12, 4)
> + st_uid = field(16, 4)
> + st_gid = field(20, 4)
> + st_rdev = field(24, 4)
> + st_size = field(28, 8)
> + st_blksize = field(36, 8)
> + st_blocks = field(44, 8)
> + st_atime = field(52, 4)
> + st_mtime = field(56, 4)
> + st_ctime = field(60, 4)
>
> return {
> "st_dev": st_dev,
> diff --git a/gdbserver/hostio.cc b/gdbserver/hostio.cc
> index b9ca27c2ff4..34def5d22da 100644
> --- a/gdbserver/hostio.cc
> +++ b/gdbserver/hostio.cc
> @@ -26,7 +26,6 @@
> #include <unistd.h>
> #include <sys/types.h>
> #include <sys/stat.h>
> -#include "gdbsupport/fileio.h"
>
> struct fd_list
> {
> @@ -465,7 +464,7 @@ handle_fstat (char *own_buf, int *new_packet_len)
> int fd, bytes_sent;
> char *p;
> struct stat st;
> - struct fio_stat fst;
> + client_state &cs = get_client_state ();
>
> p = own_buf + strlen ("vFile:fstat:");
>
> @@ -483,16 +482,31 @@ handle_fstat (char *own_buf, int *new_packet_len)
> return;
> }
>
> - host_to_fileio_stat (&st, &fst);
> -
> - bytes_sent = hostio_reply_with_data (own_buf,
> - (char *) &fst, sizeof (fst),
> - new_packet_len);
> + struct fio_stat_wide wide;
> + host_to_fileio_stat_wide (&st, &wide);
>
> - /* If the response does not fit into a single packet, do not attempt
> - to return a partial response, but simply fail. */
> - if (bytes_sent < sizeof (fst))
> - write_enn (own_buf);
> + /* Send the wide form if GDB understands it, otherwise narrow the
> + result first. */
> + if (cs.stat64_feature)
> + {
> + bytes_sent = hostio_reply_with_data (own_buf, (char *) &wide,
> + sizeof (wide), new_packet_len);
> + /* If the response does not fit into a single packet, do not attempt
> + to return a partial response, but simply fail. */
> + if (bytes_sent < sizeof (wide))
> + write_enn (own_buf);
> + }
> + else
> + {
> + struct fio_stat narrow;
> + fileio_stat_wide_to_narrow (&wide, &narrow);
> + bytes_sent = hostio_reply_with_data (own_buf, (char *) &narrow,
> + sizeof (narrow), new_packet_len);
> + /* If the response does not fit into a single packet, do not attempt
> + to return a partial response, but simply fail. */
> + if (bytes_sent < sizeof (narrow))
> + write_enn (own_buf);
> + }
> }
>
> static void
> @@ -501,8 +515,8 @@ handle_stat (char *own_buf, int *new_packet_len)
> int bytes_sent;
> char *p;
> struct stat st;
> - struct fio_stat fst;
> char filename[HOSTIO_PATH_MAX];
> + client_state &cs = get_client_state ();
>
> p = own_buf + strlen ("vFile:stat:");
>
> @@ -519,16 +533,31 @@ handle_stat (char *own_buf, int *new_packet_len)
> return;
> }
>
> - host_to_fileio_stat (&st, &fst);
> -
> - bytes_sent = hostio_reply_with_data (own_buf,
> - (char *) &fst, sizeof (fst),
> - new_packet_len);
> + struct fio_stat_wide wide;
> + host_to_fileio_stat_wide (&st, &wide);
>
> - /* If the response does not fit into a single packet, do not attempt
> - to return a partial response, but simply fail. */
> - if (bytes_sent < sizeof (fst))
> - write_enn (own_buf);
> + /* Send the wide form if GDB understands it, otherwise narrow the
> + result first. */
> + if (cs.stat64_feature)
> + {
> + bytes_sent = hostio_reply_with_data (own_buf, (char *) &wide,
> + sizeof (wide), new_packet_len);
> + /* If the response does not fit into a single packet, do not attempt
> + to return a partial response, but simply fail. */
> + if (bytes_sent < sizeof (wide))
> + write_enn (own_buf);
> + }
> + else
> + {
> + struct fio_stat narrow;
> + fileio_stat_wide_to_narrow (&wide, &narrow);
> + bytes_sent = hostio_reply_with_data (own_buf, (char *) &narrow,
> + sizeof (narrow), new_packet_len);
> + /* If the response does not fit into a single packet, do not attempt
> + to return a partial response, but simply fail. */
> + if (bytes_sent < sizeof (narrow))
> + write_enn (own_buf);
> + }
> }
>
> static void
> @@ -537,8 +566,8 @@ handle_lstat (char *own_buf, int *new_packet_len)
> int ret, bytes_sent;
> char *p;
> struct stat st;
> - struct fio_stat fst;
> char filename[HOSTIO_PATH_MAX];
> + client_state &cs = get_client_state ();
>
> p = own_buf + strlen ("vFile:lstat:");
>
> @@ -560,16 +589,31 @@ handle_lstat (char *own_buf, int *new_packet_len)
> return;
> }
>
> - host_to_fileio_stat (&st, &fst);
> -
> - bytes_sent = hostio_reply_with_data (own_buf,
> - (char *) &fst, sizeof (fst),
> - new_packet_len);
> + struct fio_stat_wide wide;
> + host_to_fileio_stat_wide (&st, &wide);
>
> - /* If the response does not fit into a single packet, do not attempt
> - to return a partial response, but simply fail. */
> - if (bytes_sent < sizeof (fst))
> - write_enn (own_buf);
> + /* Send the wide form if GDB understands it, otherwise narrow the
> + result first. */
> + if (cs.stat64_feature)
> + {
> + bytes_sent = hostio_reply_with_data (own_buf, (char *) &wide,
> + sizeof (wide), new_packet_len);
> + /* If the response does not fit into a single packet, do not attempt
> + to return a partial response, but simply fail. */
> + if (bytes_sent < sizeof (wide))
> + write_enn (own_buf);
> + }
> + else
> + {
> + struct fio_stat narrow;
> + fileio_stat_wide_to_narrow (&wide, &narrow);
> + bytes_sent = hostio_reply_with_data (own_buf, (char *) &narrow,
> + sizeof (narrow), new_packet_len);
> + /* If the response does not fit into a single packet, do not attempt
> + to return a partial response, but simply fail. */
> + if (bytes_sent < sizeof (narrow))
> + write_enn (own_buf);
> + }
> }
>
> static void
> diff --git a/gdbserver/server.cc b/gdbserver/server.cc
> index a208e24d09f..dfa24617847 100644
> --- a/gdbserver/server.cc
> +++ b/gdbserver/server.cc
> @@ -2746,6 +2746,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
> cs.single_inferior_argument = true;
> else if (feature == "multi-wp-addr+")
> cs.multiple_wp_addr_feature = true;
> + else if (feature == "stat64+")
> + cs.stat64_feature = true;
> else
> {
> /* Move the unknown features all together. */
> @@ -2762,7 +2764,7 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
> "PacketSize=%x;QPassSignals+;QProgramSignals+;"
> "QStartupWithShell+;QEnvironmentHexEncoded+;"
> "QEnvironmentReset+;QEnvironmentUnset+;"
> - "QSetWorkingDir+;binary-upload+",
> + "QSetWorkingDir+;binary-upload+;stat64+", PBUFSIZ - 1); if (target_supports_catch_syscall ()) diff --git
> a/gdbserver/server.h b/gdbserver/server.h index
> bbba3650de7..cf811f228fa 100644 --- a/gdbserver/server.h +++
> b/gdbserver/server.h @@ -192,6 +192,11 @@ struct client_state /* If
> true, memory tagging features are supported. */ bool
> memory_tagging_feature = false; + /* True if GDB supports the wide
> form of struct stat in the vFile + fstat, stat and lstat replies. When
> false, gdbserver narrows the + result to struct fio_stat before
> sending it. */ + bool stat64_feature = false; + /* If true then
> E.errtext style errors are supported everywhere, including for the
> qRcmd and m packet. When false E.errtext errors are not supported with
> qRcmd and m packets, but are still supported diff --git
> a/gdbsupport/fileio.cc b/gdbsupport/fileio.cc index
> 6d2ffb14544..bab2c4c8efe 100644 --- a/gdbsupport/fileio.cc +++
> b/gdbsupport/fileio.cc @@ -20,6 +20,7 @@ #include "fileio.h"
> #include <sys/stat.h>
> #include <fcntl.h>
> +#include "gdbsupport/errors.h"
>
> /* See fileio.h. */
>
> @@ -305,3 +306,72 @@ host_to_fileio_stat (struct stat *st, struct fio_stat *fst)
> host_to_fileio_time (st->st_mtime, fst->fst_mtime);
> host_to_fileio_time (st->st_ctime, fst->fst_ctime);
> }
> +
> +/* See fileio.h. */
> +
> +void
> +host_to_fileio_stat_wide (struct stat *st, struct fio_stat_wide *fst)
> +{
> + LONGEST blksize;
> +
> + host_to_bigendian ((LONGEST) st->st_dev, (char *) fst->fst_dev, 8);
> + host_to_bigendian ((LONGEST) st->st_ino, (char *) fst->fst_ino, 8);
> + host_to_fileio_mode (st->st_mode, fst->fst_mode);
> + host_to_bigendian ((LONGEST) st->st_nlink, (char *) fst->fst_nlink, 8);
> + host_to_fileio_uint ((long) st->st_uid, fst->fst_uid);
> + host_to_fileio_uint ((long) st->st_gid, fst->fst_gid);
> + host_to_bigendian ((LONGEST) st->st_rdev, (char *) fst->fst_rdev, 8);
> + host_to_bigendian ((LONGEST) st->st_size, (char *) fst->fst_size, 8);
> +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
> + blksize = st->st_blksize;
> +#else
> + blksize = 512;
> +#endif
> + host_to_bigendian (blksize, (char *) fst->fst_blksize, 8);
> +#if HAVE_STRUCT_STAT_ST_BLOCKS
> + host_to_bigendian ((LONGEST) st->st_blocks, (char *) fst->fst_blocks, 8);
> +#else
> + host_to_bigendian (((LONGEST) st->st_size + blksize - 1) / blksize,
> + (char *) fst->fst_blocks, 8);
> +#endif
> + host_to_bigendian ((LONGEST) st->st_atime, (char *) fst->fst_atime, 8);
> + host_to_bigendian ((LONGEST) st->st_mtime, (char *) fst->fst_mtime, 8);
> + host_to_bigendian ((LONGEST) st->st_ctime, (char *) fst->fst_ctime, 8);
> +}
> +
> +/* Copy the 8-byte value in FROM into the 4-byte field TO, warning if
> + the value does not fit. WHAT names the field, for the warning. */
> +
> +static void
> +narrow_field (const char *from, char *to, const char *what)
> +{
> + ULONGEST num = bigendian_to_host (from, 8);
> +
> + if (num != (ULONGEST) (uint32_t) num)
> + warning (_("file-i/o: %s value %s does not fit in 32 bits; "
> + "the value seen by the peer will be truncated"),
> + what, pulongest (num));
> +
> + host_to_bigendian ((LONGEST) num, to, 4);
> +}
> +
> +/* See fileio.h. */
> +
> +void
> +fileio_stat_wide_to_narrow (const struct fio_stat_wide *wide,
> + struct fio_stat *narrow)
> +{
> + narrow_field (wide->fst_dev, narrow->fst_dev, "st_dev");
> + narrow_field (wide->fst_ino, narrow->fst_ino, "st_ino");
> + memcpy (narrow->fst_mode, wide->fst_mode, 4);
> + narrow_field (wide->fst_nlink, narrow->fst_nlink, "st_nlink");
> + memcpy (narrow->fst_uid, wide->fst_uid, 4);
> + memcpy (narrow->fst_gid, wide->fst_gid, 4);
> + narrow_field (wide->fst_rdev, narrow->fst_rdev, "st_rdev");
> + memcpy (narrow->fst_size, wide->fst_size, 8);
> + memcpy (narrow->fst_blksize, wide->fst_blksize, 8);
> + memcpy (narrow->fst_blocks, wide->fst_blocks, 8);
> + narrow_field (wide->fst_atime, narrow->fst_atime, "st_atime");
> + narrow_field (wide->fst_mtime, narrow->fst_mtime, "st_mtime");
> + narrow_field (wide->fst_ctime, narrow->fst_ctime, "st_ctime");
> +}
> diff --git a/gdbsupport/fileio.h b/gdbsupport/fileio.h
> index c2592cf8c15..fa5de54dd40 100644
> --- a/gdbsupport/fileio.h
> +++ b/gdbsupport/fileio.h
> @@ -120,9 +120,11 @@ using fio_time_t = char[FIO_TIME_LEN];
> using fio_long_t = char[FIO_LONG_LEN];
> using fio_ulong_t = char[FIO_ULONG_LEN];
>
> -/* Struct stat as used in protocol. For complete independence
> - of host/target systems, it's defined as an array with offsets
> - to the members. */
> +/* Struct stat as used in the protocol. This is the format used by the
> + F-packet file-i/o protocol, and by the vFile stat packets when the
> + stat64 feature has not been negotiated. Several fields are too narrow
> + to hold the corresponding struct stat member on a 64-bit host; see
> + struct fio_stat_wide. */
>
> struct fio_stat
> {
> @@ -141,6 +143,29 @@ struct fio_stat
> fio_time_t fst_ctime;
> };
>
> +/* Struct stat as used in the wide form of the protocol. The fields
> + which cannot hold the corresponding struct stat member on a 64-bit
> + host are widened to 8 bytes. This layout is only used by the vFile
> + stat packets, and only when the stat64 feature has been negotiated
> + with the peer. */
> +
> +struct fio_stat_wide
> +{
> + fio_ulong_t fst_dev;
> + fio_ulong_t fst_ino;
> + fio_mode_t fst_mode;
> + fio_ulong_t fst_nlink;
> + fio_uint_t fst_uid;
> + fio_uint_t fst_gid;
> + fio_ulong_t fst_rdev;
> + fio_ulong_t fst_size;
> + fio_ulong_t fst_blksize;
> + fio_ulong_t fst_blocks;
> + fio_ulong_t fst_atime;
> + fio_ulong_t fst_mtime;
> + fio_ulong_t fst_ctime;
> +};
> +
> struct fio_timeval
> {
> fio_time_t ftv_sec;
> @@ -179,6 +204,20 @@ host_to_bigendian (LONGEST num, char *buf, int bytes)
> buf[i] = (num >> (8 * (bytes - i - 1))) & 0xff;
> }
>
> +/* Unpack an integer of BYTES bytes from BUF, which holds it in
> + big-endian, protocol format. */
> +
> +static inline ULONGEST
> +bigendian_to_host (const char *buf, int bytes)
> +{
> + ULONGEST num = 0;
> + int i;
> +
> + for (i = 0; i < bytes; ++i)
> + num = (num << 8) | (unsigned char) buf[i];
> + return num;
> +}
> +
> /* Pack a host-format integer into an fio_uint_t. */
>
> static inline void
> @@ -199,4 +238,13 @@ host_to_fileio_time (time_t num, fio_time_t fnum)
>
> extern void host_to_fileio_stat (struct stat *st, struct fio_stat *fst);
>
> +/* Pack a host-format struct stat into a struct fio_stat_wide. */
> +extern void host_to_fileio_stat_wide (struct stat *st,
> + struct fio_stat_wide *fst);
> +
> +/* Narrow a struct fio_stat_wide into a struct fio_stat, warning if any
> + value does not fit. */
> +extern void fileio_stat_wide_to_narrow (const struct fio_stat_wide *wide,
> + struct fio_stat *narrow);
> +
> #endif /* GDBSUPPORT_FILEIO_H */
[-- Attachment #2: Type: text/html, Size: 34154 bytes --]
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-16 13:23 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-04 8:32 [PATCH v2] gdbserver: widen the stat fields returned by the vFile packets Abhay Kandpal
2026-09-16 13:22 ` Abhay Kandpal
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox