Warning when using separate debug info file
LRN
lrn1986@gmail.com
Wed Apr 17 20:06:00 GMT 2019
On 17.04.2019 20:37, Eli Zaretskii wrote:
> I tried debugging a program on MS-Windows after moving the debug info
> to a separate file
> When I then invoke GDB, it does find the symbols, but emits a warning:
>
> Reading symbols from ./e.exe...Reading symbols from d:\foo\bar\e.debug...
> warning: section .gnu_debuglink not found in d:\foo\bar\e.debug
>
I'm too lazy to explain this right now. Here's a Python program (attached) that
creates separate debug info files that do not produce this warning.
I've reported it to the bugtracker, but no one seems to be interested in fixing
this, and i have no idea why this happens (although i do have a hypothesis).
-------------- next part --------------
#!/mingw/bin/python
# -*- coding: utf-8 -*-
# split-debug.py - splits debug symbols from executables into separate files
# Copyright © 2012 LRN
#
# 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/>.
from __future__ import print_function
import os
import sys
import subprocess
import hashlib
import stat
import re
import struct
import platform
def which (program):
import os
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
elif not program.endswith ('.exe') and is_exe (program + '.exe'):
return program + '.exe'
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
elif not program.endswith ('.exe') and is_exe (exe_file + '.exe'):
return exe_file + '.exe'
return None
__known_binary_files = {}
__known_non_binary_files = {}
__linked_binary_files = {}
__target_pe_magic = None
if os.name == 'nt':
import win32file
def get_read_handle (filename):
if os.path.isdir(filename):
dwFlagsAndAttributes = win32file.FILE_FLAG_BACKUP_SEMANTICS
else:
dwFlagsAndAttributes = 0
return win32file.CreateFile (
filename,
win32file.GENERIC_READ,
win32file.FILE_SHARE_READ,
None,
win32file.OPEN_EXISTING,
dwFlagsAndAttributes,
None
)
def get_unique_id (hFile):
(
attributes,
created_at, accessed_at, written_at,
volume,
file_hi, file_lo,
n_links,
index_hi, index_lo
) = win32file.GetFileInformationByHandle (hFile)
return volume, index_hi, index_lo
def is_same_file (filename1, filename2):
hFile1 = get_read_handle (filename1)
hFile2 = get_read_handle (filename2)
are_equal = (get_unique_id (hFile1) == get_unique_id (hFile2))
hFile2.Close ()
hFile1.Close ()
return are_equal
def nt_is_link (filename):
bs_filename = filename.replace ('/', '\\')
dirname = os.path.dirname (bs_filename)
p = subprocess.Popen ([os.environ['ComSpec'], '/C', 'dir', dirname], stdout=subprocess.PIPE)
o, e = p.communicate ()
if p.returncode != 0:
return True
bn = os.path.basename (filename)
if '<SYMLINK> ' + bn + ' [' in o and ' ' + bn + '\n' not in o:
return True
return False
else:
def is_same_file (filename1, filename2):
s1 = os.stat (filename1)
s2 = os.stat (filename2)
are_equal = s1.st_ino == s2.st_ino
return are_equal
def is_binary_file (f):
if f in __known_non_binary_files:
return False
if f in __known_binary_files:
return True
return is_pe_file (f)
def is_pe_file (f):
head = []
try:
with open (f, 'rb') as r:
b = r.read (30*2 + 4)
if len (b) != 30*2 + 4:
__known_non_binary_files[f] = False
return False
signature, bytes_in_last_block, blocks_in_file, num_relocs, header_paragraphs, min_extra_paragraphs, max_extra_paragraphs, ss, sp, checksum, ip, cs, \
reloc_table_offset, overlay_number, reserved1, oemid, oeminfo, reserved2, exe_offset = struct.unpack ('2s HHHHHHHHHHHHH 8s HH 20s i', b)
if signature != 'MZ':
__known_non_binary_files[f] = False
return False
b = r.read (4)
r.seek (exe_offset)
b = r.read (4)
if b != 'PE\0\0':
__known_non_binary_files[f] = False
return False
b = r.read (20)
if len (b) != 20:
__known_non_binary_files[f] = False
return False
machine, number_of_sections, time_date_stamp, pointer_to_symbol_table, number_of_symbols, size_of_optional_header, characteristics = struct.unpack ('H H I I I H H', b)
b = r.read (96)
if len (b) != 96:
__known_non_binary_files[f] = False
return False
magic, major_linker_ver, minor_linker_ver, size_of_code, size_of_init_data, size_of_unint_data, address_of_entry_point, \
base_of_code, base_of_data, image_base, section_alignment, file_alignment, major_os_version, minor_os_version, major_image_version, minor_image_version, \
major_subsys_version, minor_subsys_version, w32_version_value, size_of_image, size_of_headers, checksum, subsystem, dll_characteristics, size_of_stack_reserve, \
size_of_stack_commit, size_of_heap_reserve, size_of_heap_commit, loader_flags, number_of_rva_and_sizes = struct.unpack ('H BB IIIIIIIII HHHHHH IIII HH IIIIII', b)
if magic != __target_pe_magic:
__known_non_binary_files[f] = False
return False
except:
# Fails for files with weird names (they are usually not PE binaries anyway, so return False)
return False
__known_binary_files[f] = True
return True
#def is_archive_file (f):
# head = []
# with open (f, 'rb') as r:
# head = r.read (8)
# if len (head) == 8 and head == '!<arch>\n':
# __known_binary_files[f] = True
# return True
# __known_non_binary_files[f] = False
# return False
def main ():
global __target_pe_magic
dir_to_scan = sys.argv[1]
nostrip = []
nostrip_unneeded = []
ignore = []
target = None
for a in sys.argv[1:]:
if a[:10] == '--nostrip=':
nostrip.append (a[10:])
elif a[:19] == '--nostrip-unneeded=':
nostrip_unneeded.append (a[19:])
elif a[:9] == '--ignore=':
ignore.append (a[9:])
elif a[:9] == '--target=':
target = a[9:]
if target is None:
cpy = "objcopy"
if platform.architecture ()[0] == '32bit':
__target_pe_magic = 0x10b
else:
__target_pe_magic = 0x20b
else:
cpy = target + '-objcopy'
if 'x86_64' in target:
__target_pe_magic = 0x20b
else:
__target_pe_magic = 0x10b
objcopy = which (cpy)
if objcopy is None:
print ("Failed to find {}".format (cpy))
return -1
for root, dirs, files in os.walk (dir_to_scan):
for fn in files:
f = os.path.join (root, fn)
if is_binary_file (f):
if fn[-4:] == '.dbg':
continue
if os.name == 'nt':
is_link = nt_is_link (f)
else:
st = os.lstat (f)
is_link = stat.S_ISLNK (st.st_mode)
if is_link:
print ("Skipping, since this file is a symlink - {}".format (f))
continue
print ("Processing file {} in directory {}".format (fn, root))
rc = process_binary_file (root, fn, f, nostrip, nostrip_unneeded, ignore, objcopy)
if not rc == 0:
print ("ERROR: {}".format (rc))
continue
return 0
def get_file_hash (f):
h = hashlib.md5 ()
with open (f, 'rb') as src:
while True:
r = src.read (1024 * 64)
if not r:
break
h.update (r)
return h.digest ()
def process_binary_file (root, fn, f, nostrip_list, nostrip_unneeded_list, ignore_list, objcopy):
dbg = "{}.dbg".format (fn)
dbg_abs = os.path.join (root, dbg)
if os.path.exists (dbg_abs) and os.path.isfile (dbg_abs) and is_binary_file (dbg_abs):
print ("Skipping: dbg file already exists: {}".format (dbg_abs))
return 0
h = get_file_hash (f)
linked = __linked_binary_files.get (h, None)
if linked is not None and is_same_file (f, linked[0]):
print ("Skipping: file {} is already stripped as {} and linked to {}".format (f, linked[0], linked[1]))
return 0
# This is can be done in a much shorter way, but gdb will warn about missing
# .gnu_debuglink section in _.dbg_ file (because it does not have one;
# but it isn't required to have one!)
# Here's what it does:
# create a dbg file with proper debug info:
# objcopy --only-keep-debug orig dbg
# add a link to the original file, pointing at the dbg file (adds the
# .gnu_debuglink section)
# objcopy --add-gnu-debuglink="dbg" orig
# re-create the dbg file; this time it will ALSO have the .gnu_debuglink
# section
# objcopy --only-keep-debug orig dbg
# remove old .gnu_debuglink section from the original file
# objcopy --remove-section=.gnu_debuglink orig
# strip debug-info from the original file
# objcopy --strip-debug orig
# add a new .gnu_debuglink section to the original file
# objcopy --add-gnu-debuglink="dbg" orig
# This way dbg file gets a .gnu_debuglink section (doesn't matter where
# it's pointing), and its contents pass the CRC32 check
# Shorter way:
# objcopy --only-keep-debug orig dbg
# objcopy --strip-debug orig
# objcopy --add-gnu-debuglink="dbg" orig
def popen_and_print (l):
print ('"' + '" "'.join (l) + '"')
sys.stdout.flush ()
return subprocess.Popen (l)
for ign in ignore_list:
if f.endswith (ign):
print ("Ignore {}".format (f))
return 0
print ("Separating debug info from {} into {}".format (f, dbg_abs))
oc = popen_and_print ([objcopy, '--only-keep-debug', f, dbg_abs])
oc.communicate ()
if not oc.returncode == 0:
return oc.returncode
print ("Creating a debuginfo link to {} in {}".format (dbg_abs, f))
oc = popen_and_print ([objcopy, '--add-gnu-debuglink={}'.format (dbg_abs), f])
oc.communicate ()
if not oc.returncode == 0:
return oc.returncode
print ("Separating (again) debug info from {} into {}".format (f, dbg_abs))
oc = popen_and_print ([objcopy, '--only-keep-debug', f, dbg_abs])
oc.communicate ()
if not oc.returncode == 0:
return oc.returncode
print ("Removing old .gnu_debuglink section from {}".format (f))
st = popen_and_print ([objcopy, '--remove-section=.gnu_debuglink', f])
st.communicate ()
if not st.returncode == 0:
return oc.returncode
do_strip = True
do_strip_unneeded = True
for nostrip in nostrip_list:
if f.endswith (nostrip):
do_strip = False
break
for nostrip in nostrip_unneeded_list:
if f.endswith (nostrip):
do_strip_unneeded = False
break
if do_strip:
strip_unneeded = []
if not do_strip_unneeded:
strip_unneeded.append ('--strip-unneeded')
print ("Stripping debug info from {}".format (f))
st = popen_and_print ([objcopy, '--strip-debug'] + strip_unneeded + [f])
st.communicate ()
if not st.returncode == 0:
return st.returncode
else:
print ("Not stripping {}".format (f))
print ("Creating (again) a debuginfo link to {} in {}".format (dbg_abs, f))
oc = popen_and_print ([objcopy, '--add-gnu-debuglink={}'.format (dbg_abs), f])
oc.communicate ()
if not oc.returncode == 0:
return oc.returncode
h = get_file_hash (f)
__linked_binary_files[h] = (f, dbg_abs)
return 0
if __name__ == "__main__":
sys.exit (main ())
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: OpenPGP digital signature
URL: <http://sourceware.org/pipermail/gdb-patches/attachments/20190417/dbff1a86/attachment.sig>
More information about the Gdb-patches
mailing list