--- ./build/files.c.orig 2017-02-16 09:52:49.292092380 +0000 +++ ./build/files.c 2017-03-22 13:32:42.911865500 +0000 @@ -21,6 +21,10 @@ #include #include +#if HAVE_GELF_H +#include +#endif + #include "rpmio/rpmio_internal.h" /* XXX rpmioSlurp */ #include "misc/fts.h" #include "lib/rpmfi_internal.h" /* XXX fi->apath */ @@ -2155,13 +2159,302 @@ exit: return rc; } +#if HAVE_GELF_H && HAVE_LIBELF +/* Query the build-id from the ELF file NAME and store it in the newly + allocated *build_id array of size *build_id_size. Returns -1 on + error. */ + +static int +getELFBuildId (const char *name, + unsigned char **id, size_t *id_size) +{ + int fd, i; + Elf *elf; + GElf_Ehdr ehdr; + Elf_Data *build_id = NULL; + size_t build_id_offset = 0, build_id_size = 0; + + /* Now query the build-id of the file and add the + corresponding links in the .build-id tree. + The following code is based on tools/debugedit.c. */ + fd = open (name, O_RDONLY); + if (fd < 0) + return -1; + elf = elf_begin (fd, ELF_C_READ_MMAP, NULL); + if (elf == NULL) + { + fprintf (stderr, "cannot open ELF file: %s", + elf_errmsg (-1)); + close (fd); + return -1; + } + if (elf_kind (elf) != ELF_K_ELF + || gelf_getehdr (elf, &ehdr) == NULL + || (ehdr.e_type != ET_DYN + && ehdr.e_type != ET_EXEC + && ehdr.e_type != ET_REL)) + { + elf_end (elf); + close (fd); + return -1; + } + for (i = 0; i < ehdr.e_shnum; ++i) + { + Elf_Scn *s = elf_getscn (elf, i); + GElf_Shdr shdr; + Elf_Data *data; + Elf32_Nhdr nh; + Elf_Data dst = + { + .d_version = EV_CURRENT, .d_type = ELF_T_NHDR, + .d_buf = &nh, .d_size = sizeof nh + }; + Elf_Data src = dst; + + gelf_getshdr (s, &shdr); + /* LD creates .note.gnu.build-id with SHF_ALLOC but the DWZ + common debuginfo only file only has non-allocated sections. */ + if (shdr.sh_type != SHT_NOTE) + continue; + + /* Look for a build-ID note here. */ + data = elf_rawdata (s, NULL); + src.d_buf = data->d_buf; + assert (sizeof (Elf32_Nhdr) == sizeof (Elf64_Nhdr)); + while ((unsigned char *)data->d_buf + data->d_size - (unsigned char *)src.d_buf > (int) sizeof nh + && elf32_xlatetom (&dst, &src, ehdr.e_ident[EI_DATA])) + { + Elf32_Word len = sizeof nh + nh.n_namesz; + len = (len + 3) & ~3; + + if (nh.n_namesz == sizeof "GNU" && nh.n_type == 3 + && !memcmp ((unsigned char *)src.d_buf + sizeof nh, "GNU", sizeof "GNU")) + { + build_id = data; + build_id_offset = (unsigned char *)src.d_buf + len - (unsigned char *)data->d_buf; + build_id_size = nh.n_descsz; + break; + } + + len += nh.n_descsz; + len = (len + 3) & ~3; + src.d_buf = (unsigned char *)src.d_buf + len; + } + + if (build_id != NULL) + break; + } + + if (build_id == NULL) + return -1; + + *id = malloc (build_id_size); + *id_size = build_id_size; + memcpy (*id, (unsigned char *)build_id->d_buf + build_id_offset, build_id_size); + + elf_end (elf); + close (fd); + + return 0; +} + + +static rpmTag copyTagsForDebug[] = { + RPMTAG_EPOCH, + RPMTAG_VERSION, + RPMTAG_RELEASE, + RPMTAG_LICENSE, + RPMTAG_PACKAGER, + RPMTAG_DISTRIBUTION, + RPMTAG_DISTURL, + RPMTAG_VENDOR, + RPMTAG_ICON, + RPMTAG_URL, + RPMTAG_CHANGELOGTIME, + RPMTAG_CHANGELOGNAME, + RPMTAG_CHANGELOGTEXT, + RPMTAG_PREFIXES, + RPMTAG_RHNPLATFORM, + RPMTAG_OS, + RPMTAG_DISTTAG, + RPMTAG_CVSID, + RPMTAG_ARCH, + 0 +}; + +/* Add a new debuginfo package based on PKG with FILES. */ + +static Package addDebuginfoPackage(rpmSpec spec, Package pkg, ARGV_t files) +{ + const char *name; + char tmp[1024]; + Package dbg = newPackage(NULL, spec->pool, &spec->packages); + name = headerGetString(pkg->header, RPMTAG_NAME); + /* Set name, summary and group. */ + snprintf(tmp, 1024, "%s-debuginfo", name); + headerPutString(dbg->header, RPMTAG_NAME, tmp); + snprintf(tmp, 1024, "Debug information for package %s", name); + headerPutString(dbg->header, RPMTAG_SUMMARY, tmp); + snprintf(tmp, 1024, "This package provides debug information for package %s.\n" + "Debug information is useful when developing applications that use this\n" + "package or when debugging this package.", name); + headerPutString(dbg->header, RPMTAG_DESCRIPTION, tmp); + headerPutString(dbg->header, RPMTAG_GROUP, "Development/Debug"); + /* Inherit other tags from parent. */ + headerCopyTags(spec->packages->header, + dbg->header, copyTagsForDebug); + + /* Add self-provides */ + dbg->ds = rpmdsThis(dbg->header, RPMTAG_REQUIRENAME, RPMSENSE_EQUAL); + addPackageProvides(dbg); + + /* Build up the files list. */ + dbg->fileList = files; + return dbg; +} + +/* Process the filelist of PKG and see to eventually create a debuginfo + packge for it. */ + +static Package processDebuginfo(rpmSpec spec, Package pkg, char *buildroot) +{ + const char *a; + + elf_version(EV_CURRENT); + a = headerGetString(pkg->header, RPMTAG_ARCH); + if (strcmp(a, "noarch") != 0) + { + rpmfi fi = rpmfilesIter(pkg->cpioList, RPMFI_ITER_FWD); + char tmp[1024]; + const char *name; + ARGV_t files = NULL; + int seen_build_id = 0; + + /* Check if the current package has files with debug info + and record them. */ + fi = rpmfiInit(fi, 0); + while (rpmfiNext(fi) >= 0) + { + int i; + unsigned char *build_id = NULL; + size_t build_id_size = 0; + struct stat sbuf; + + name = rpmfiFN(fi); + /* Pre-pend %buildroot/usr/lib/debug and append .debug. */ + snprintf(tmp, 1024, "%s/usr/lib/debug%s.debug", + buildroot, name); + /* If that file exists we have debug information for it. */ + if (access(tmp, F_OK) != 0) + continue; + + /* Append the file list preamble. */ + if (!files) + { + argvAdd(&files, "%defattr(-,root,root)"); + argvAdd(&files, "%dir /usr/lib/debug"); + } + /* Add the files main debug-info file. */ + snprintf(tmp, 1024, "/usr/lib/debug/%s.debug", name); + argvAdd(&files, tmp); + + snprintf(tmp, 1024, "%s%s", buildroot, name); + /* Do not bother to check build-ids for symbolic links. + We'll handle them for the link target. */ + if (lstat(tmp, &sbuf) == -1 || S_ISLNK(sbuf.st_mode)) + continue; + + /* Try to gather the build-id from the binary. */ + if (getELFBuildId(tmp, &build_id, &build_id_size) == -1) + continue; + + /* If we see build-id links for the first time add the + directory. */ + if (!seen_build_id) + { + seen_build_id = 1; + argvAdd(&files, "%dir /usr/lib/debug/.build-id"); + } + + /* From the build-id construct the two links pointing back + to the debug information file and the binary. */ + snprintf(tmp, 1024, "/usr/lib/debug/.build-id/%02x/", + build_id[0]); + for (i = 1; i < build_id_size; ++i) + sprintf(tmp + strlen(tmp), "%02x", build_id[i]); + argvAdd(&files, tmp); + sprintf(tmp + strlen(tmp), ".debug"); + argvAdd(&files, tmp); + + free(build_id); + } + + /* If there are debuginfo files for this package add a + new debuginfo package. */ + if (files) + return addDebuginfoPackage (spec, pkg, files); + } + return NULL; +} + + +static char *addDebugDWZ(ARGV_t *filesp, char *buildroot) +{ + char tmp[1024]; + struct stat sbuf; + char *dwz_dbg_buildid = NULL; + DIR *d; + struct dirent *de; + int i; + + snprintf(tmp, 1024, "%s%s", buildroot, "/usr/lib/debug/.dwz"); + if (lstat(tmp, &sbuf) != 0 || !S_ISDIR(sbuf.st_mode)) + return NULL; + d = opendir(tmp); + if (!d) + return NULL; + + argvAdd(filesp, "/usr/lib/debug/.dwz"); + while ((de = readdir (d))) { + unsigned char *build_id = NULL; + size_t build_id_size = 0; + + snprintf(tmp, 1024, "%s/usr/lib/debug/.dwz/%s", buildroot, de->d_name); + if (lstat(tmp, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)) + continue; + if (getELFBuildId(tmp, &build_id, &build_id_size) == -1) + continue; + snprintf(tmp, 1024, "/usr/lib/debug/.build-id/%02x/", build_id[0]); + for (i = 1; i < build_id_size; ++i) + sprintf(tmp + strlen(tmp), "%02x", build_id[i]); + sprintf(tmp + strlen(tmp), ".debug"); + argvAdd(filesp, tmp); + if (!dwz_dbg_buildid) { + for (i = 0; i < build_id_size; ++i) + sprintf(tmp + 2 * i, "%02x", build_id[i]); + dwz_dbg_buildid = xstrdup(tmp); + } + free(build_id); + } + closedir(d); + return dwz_dbg_buildid; +} + +#endif + rpmRC processBinaryFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, int installSpecialDoc, int test) { Package pkg; rpmRC rc = RPMRC_OK; + char *buildroot; + Package first_dbg = NULL, dwz_dbg = NULL; + int processing_dbg = 0; + int main_pkg_got_dbg = 0; + char *dwz_dbg_buildid = NULL; check_fileList = newStringBuf(); + buildroot = rpmGenPath(spec->rootDir, spec->buildRoot, NULL); genSourceRpmName(spec); for (pkg = spec->packages; pkg != NULL; pkg = pkg->next) { @@ -2179,8 +2472,40 @@ rpmRC processBinaryFiles(rpmSpec spec, r rpmlog(RPMLOG_NOTICE, _("Processing files: %s\n"), nvr); free(nvr); - if ((rc = processPackageFiles(spec, pkgFlags, pkg, installSpecialDoc, test)) != RPMRC_OK || - (rc = rpmfcGenerateDepends(spec, pkg)) != RPMRC_OK) +#if HAVE_GELF_H && HAVE_LIBELF + if (pkg == first_dbg) { + /* If we have multiple debug packages then we put + DWZ generated files into %name-debuginfo which + may already exist. Otherwise put the DWZ data + into the only debug package. */ + processing_dbg = 1; + if (!first_dbg->next || main_pkg_got_dbg) { + dwz_dbg_buildid = addDebugDWZ(&first_dbg->fileList, buildroot); + dwz_dbg = pkg; + } else { + ARGV_t files = NULL; + dwz_dbg_buildid = addDebugDWZ(&files, buildroot); + dwz_dbg = addDebuginfoPackage(spec, spec->packages, files); + } + } +#endif + if ((rc = processPackageFiles(spec, pkgFlags, pkg, installSpecialDoc, test)) != RPMRC_OK) + goto exit; +#if HAVE_GELF_H && HAVE_LIBELF + if (!processing_dbg) { + Package dbg = processDebuginfo(spec, pkg, buildroot); + if (dbg && !first_dbg) { + first_dbg = dbg; + if (pkg == spec->packages) + main_pkg_got_dbg = 1; + } + } + /* If we have DWZ info and it is not in PKG then add a requires. */ + if (dwz_dbg_buildid && pkg != dwz_dbg) + addReqProv(pkg, RPMTAG_REQUIRENAME, + "debuginfo(build-id)", dwz_dbg_buildid, RPMSENSE_EQUAL, 0); +#endif + if ((rc = rpmfcGenerateDepends(spec, pkg)) != RPMRC_OK) goto exit; a = headerGetString(pkg->header, RPMTAG_ARCH); @@ -2215,6 +2540,7 @@ rpmRC processBinaryFiles(rpmSpec spec, r } exit: check_fileList = freeStringBuf(check_fileList); + _free(dwz_dbg_buildid); return rc; } --- ./build/parseSpec.c.orig 2017-03-22 12:10:11.304029953 +0000 +++ ./build/parseSpec.c 2017-03-22 12:10:20.142010341 +0000 @@ -564,7 +564,7 @@ static void initSourceHeader(rpmSpec spe } /* Add extra provides to package. */ -static void addPackageProvides(Package pkg) +void addPackageProvides(Package pkg) { const char *arch, *name; char *evr, *isaprov; --- ./build/rpmbuild_internal.h.orig 2017-02-16 09:40:09.788649545 +0000 +++ ./build/rpmbuild_internal.h 2017-03-22 12:10:20.143010339 +0000 @@ -442,6 +442,13 @@ int addReqProv(Package pkg, rpmTagVal ta /** \ingroup rpmbuild + * Add self-provides to package. + * @param pkg package + */ +RPM_GNUC_INTERNAL +void addPackageProvides(Package pkg); + +/** \ingroup rpmbuild * Add rpmlib feature dependency. * @param pkg package * @param feature rpm feature name (i.e. "rpmlib(Foo)" for feature Foo) --- ./macros.in.orig 2017-03-22 12:10:11.307029946 +0000 +++ ./macros.in 2017-03-22 12:10:20.143010339 +0000 @@ -186,24 +186,10 @@ # Template for debug information sub-package. %debug_package \ %global __debug_package 1\ -%package debuginfo\ -Summary: Debug information for package %{name}\ -Group: Development/Debug\ -AutoReq: 0\ -AutoProv: 1\ -#Requires: %{?!debug_package_requires:%{name} = %{version}-%{release}}%{?debug_package_requires}\ -%description debuginfo\ -This package provides debug information for package %{name}.\ -Debug information is useful when developing applications that use this\ -package or when debugging this package.\ -%files debuginfo -f debugfiles.list\ -%defattr(-,root,root)\ -\ %package debugsource\ Summary: Debug sources for package %{name}\ Group: Development/Debug\ AutoReqProv: 0\ -Requires: %{name}-debuginfo = %{version}-%{release}\ %description debugsource\ This package provides debug sources for package %{name}.\ Debug sources are useful when developing applications that use this\ --- ./scripts/find-debuginfo.sh.orig 2017-03-22 12:10:11.303029955 +0000 +++ ./scripts/find-debuginfo.sh 2017-03-22 12:10:20.144010337 +0000 @@ -220,6 +220,11 @@ debug_link() # Provide .2, .3, ... symlinks to all filename instances of this build-id. make_id_dup_link() { + # See https://bugzilla.redhat.com/show_bug.cgi?id=641377 for the reasoning, + # but it has seveal drawbacks as we would need to split the .1 suffixes into + # different subpackages and it's about impossible to predict the number + # -> perhaps later + return local id="$1" file="$2" idfile local n=1 @@ -424,19 +429,11 @@ if $run_dwz && type dwz >/dev/null 2>&1 fi fi -# For each symlink whose target has a .debug file, -# make a .debug symlink to that file. -find "$RPM_BUILD_ROOT" ! -path "${debugdir}/*" -type l -print | -while read f -do - t=$(readlink -m "$f").debug - f=${f#$RPM_BUILD_ROOT} - t=${t#$RPM_BUILD_ROOT} - if [ -f "$debugdir$t" ]; then - echo "symlinked /usr/lib/debug$t to /usr/lib/debug${f}.debug" - debug_link "/usr/lib/debug$t" "${f}.debug" - fi -done +# We used to make a .debug symlink for each symlink whose target +# has a .debug file to that file. This is not necessary because +# the debuglink section contains only the destination of those links. +# Creating those links anyway results in debuginfo packages for +# devel packages just because of the .so symlinks in them. if [ -s "$SOURCEFILE" ]; then mkdir -p "${RPM_BUILD_ROOT}/usr/src/debug"