mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-12-05 15:15:42 +00:00
In the following commits I added the target_fileio_stat function, and the target_ops::fileio_stat member function: *08a115cc1cgdb: add target_fileio_stat, but no implementations yet *3055e3d2f1gdb: add GDB side target_ops::fileio_stat implementation *6d45af96eagdbserver: add gdbserver support for vFile::stat packet *22836ca885gdb: check for multiple matching build-id files Unfortunately I messed up, despite being called 'stat' these function actually performed an 'lstat'. The 'lstat' is the correct (required) implementation, it's the naming that is wrong. In the previous commit I fixed the naming within GDB, renaming 'stat' to 'lstat' throughout. However, in order to support target_fileio_stat (as was) on remote targets, the above patches added the vFile:stat packet, which actually performed an 'lstat' call. This is really quite unfortunate, and I'd like to do as much as I can to try and clean up this mess. But I'm mindful that changing packets is not really the done thing. So, this commit doesn't change anything. Instead, this commit adds vFile:lstat as a new packet. Currently, this packet is handled identically as vFile:stat, the packet performs an 'lstat' call. I then update GDB to send the new vFile:lstat instead of vFile:stat for the remote_target::fileio_lstat implementation. After this commit GDB will never send the vFile:stat packet. However, I have retained the 'set remote hostio-stat-packet' control flag, just in case someone was trying to set this somewhere. Then there's one test in the testsuite which used to disable the vFile:stat packet, that test is updated to now disable vFile:lstat. There's a new test that does a more direct test of vFile:lstat. This new test can be extended to also test vFile:stat, but that is left for the next commit. And so, after this commit, GDB sends the new vFile:lstat packet in order to implement target_ops::fileio_lstat. The new packet is more clearly documented than vFile:stat is. But critically, this change doesn't risk breaking any other clients or servers that implement GDB's remote protocol. Reviewed-By: Eli Zaretskii <eliz@gnu.org> Approved-By: Tom Tromey <tom@tromey.com>
171 lines
5.6 KiB
Python
171 lines
5.6 KiB
Python
# Copyright (C) 2025 Free Software Foundation, Inc.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os, stat
|
|
|
|
|
|
# Hex encode INPUT_STRING in the same way that GDB does. Each
|
|
# character in INPUT_STRING is expanded to its two digit hex
|
|
# representation in the returned string.
|
|
#
|
|
# Only ASCII characters may appear in INPUT_STRING, this is more
|
|
# restrictive than GDB, but is good enough for testing.
|
|
def hex_encode(input_string):
|
|
byte_string = input_string.encode("ascii")
|
|
hex_string = byte_string.hex()
|
|
return hex_string
|
|
|
|
|
|
# Binary remote data packets can contain some escaped bytes. Decode
|
|
# the packet now.
|
|
def unescape_remote_data(buf):
|
|
escaped = False
|
|
res = bytearray()
|
|
for b in buf:
|
|
if escaped:
|
|
res.append(b ^ 0x20)
|
|
escaped = False
|
|
elif b == ord("}"):
|
|
escaped = True
|
|
else:
|
|
res.append(b)
|
|
res = bytes(res)
|
|
return res
|
|
|
|
|
|
# Decode the results of a remote stat like command from BUF. Returns
|
|
# None if BUF is not a valid stat result (e.g. if it indicates an
|
|
# error, or the buffer is too short). If BUF is valid then the fields
|
|
# are decoded according to the GDB remote protocol and placed into a
|
|
# dictionary, this dictionary is then returned.
|
|
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
|
|
):
|
|
l = len(buf)
|
|
print(f"decode_stat_reply failed: {buf}\t(length = {l})")
|
|
return None
|
|
|
|
# Discard the 'F40;' prefix. The rest is the 64 bytes of data to
|
|
# be decoded.
|
|
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)
|
|
|
|
return {
|
|
"st_dev": st_dev,
|
|
"st_ino": st_ino,
|
|
"st_mode": st_mode,
|
|
"st_nlink": st_nlink,
|
|
"st_uid": st_uid,
|
|
"st_gid": st_gid,
|
|
"st_rdev": st_rdev,
|
|
"st_size": st_size,
|
|
"st_blksize": st_blksize,
|
|
"st_blocks": st_blocks,
|
|
"st_atime": st_atime,
|
|
"st_mtime": st_mtime,
|
|
"st_ctime": st_ctime,
|
|
}
|
|
|
|
|
|
# Perform an lstat of remote file FILENAME, and create a dictionary of
|
|
# the results, the keys are the fields of the stat structure.
|
|
def remote_lstat(filename):
|
|
conn = gdb.selected_inferior().connection
|
|
if not isinstance(conn, gdb.RemoteTargetConnection):
|
|
raise gdb.GdbError("connection is the wrong type")
|
|
|
|
filename_hex = hex_encode(filename)
|
|
reply = conn.send_packet("vFile:lstat:%s" % filename_hex)
|
|
|
|
stat = decode_stat_reply(reply)
|
|
return stat
|
|
|
|
|
|
# Convert a stat_result object to a dictionary that should match the
|
|
# dictionary built from the remote protocol reply.
|
|
def stat_result_to_dict(res):
|
|
# GDB doesn't support the S_IFLNK flag for the remote protocol, so
|
|
# clear that flag in the local results.
|
|
if stat.S_ISLNK(res.st_mode):
|
|
st_mode = stat.S_IMODE(res.st_mode)
|
|
else:
|
|
st_mode = res.st_mode
|
|
|
|
# GDB returns an integer for these fields, while Python returns a
|
|
# floating point value. Convert back to an integer to match GDB.
|
|
st_atime = int(res.st_atime)
|
|
st_mtime = int(res.st_mtime)
|
|
st_ctime = int(res.st_ctime)
|
|
|
|
return {
|
|
"st_dev": res.st_dev,
|
|
"st_ino": res.st_ino,
|
|
"st_mode": st_mode,
|
|
"st_nlink": res.st_nlink,
|
|
"st_uid": res.st_uid,
|
|
"st_gid": res.st_gid,
|
|
"st_rdev": res.st_rdev,
|
|
"st_size": res.st_size,
|
|
"st_blksize": res.st_blksize,
|
|
"st_blocks": res.st_blocks,
|
|
"st_atime": st_atime,
|
|
"st_mtime": st_mtime,
|
|
"st_ctime": st_ctime,
|
|
}
|
|
|
|
|
|
# Perform an lstat of local file FILENAME, and create a dictionary of
|
|
# the results, the keys are the fields of the stat structure.
|
|
def local_lstat(filename):
|
|
res = os.lstat(filename)
|
|
return stat_result_to_dict(res)
|
|
|
|
|
|
# Perform a remote lstat using GDB, and a local lstat using os.lstat.
|
|
# Compare the results to check they are the same.
|
|
#
|
|
# For this test to work correctly, gdbserver, and GDB (where this
|
|
# Python script is running), must see the same filesystem.
|
|
def check_lstat(filename):
|
|
s1 = remote_lstat(filename)
|
|
s2 = local_lstat(filename)
|
|
|
|
print(f"s1 = {s1}")
|
|
print(f"s2 = {s2}")
|
|
|
|
assert s1 == s2
|
|
print("PASS")
|