diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index cc782981ea18..10bfc496902f 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -672,7 +672,7 @@ "jdk.graal.compiler.util to org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.configure", "jdk.graal.compiler.core.common to org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.objectfile", "jdk.graal.compiler.debug to org.graalvm.nativeimage.objectfile", - "jdk.graal.compiler.nodes.graphbuilderconf to org.graalvm.nativeimage.driver,org.graalvm.nativeimage.librarysupport", + "jdk.graal.compiler.nodes.graphbuilderconf to org.graalvm.nativeimage.driver,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.svm.niutils", "jdk.graal.compiler.phases.common to org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.configure", "jdk.graal.compiler.serviceprovider to org.graalvm.nativeimage.driver,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.diagnostics,org.graalvm.nativeimage.objectfile", "jdk.graal.compiler.util.json to org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.configure,org.graalvm.nativeimage.driver", diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 86b44f756d64..6b27059e67d3 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1792,6 +1792,18 @@ def _native_image_launcher_extra_jvm_args(): extra_jvm_args=_native_image_launcher_extra_jvm_args(), home_finder=False, ), + mx_sdk_vm.LauncherConfig( + use_modules='image', + main_module="org.graalvm.nativeimage.svm.niutils", + destination="bin/", + jar_distributions=["substratevm:SVM_SBOM_EXTRACT"], + main_class="com.oracle.svm.niutils.NativeImageUtils", + build_args=driver_build_args + svm_experimental_options([ + '-H:+UseLibExtractSbom', + '-H:NativeLinkerOption=-lelf', + ]), + home_finder=False, + ), ], library_configs=[ mx_sdk_vm.LibraryConfig( diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 69da6ac4a200..aae8b0eb54d3 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -945,6 +945,28 @@ "jacoco" : "exclude", }, + "com.oracle.svm.native.extractsbom": { + "subDir": "src", + "native": "static_lib", + "deliverable" : "extract_sbom", + "os_arch": { + "linux": { + "": { + "cflags": ["-g", "-Wall" ], + }, + }, + "": { + "": { + "ignore": "only supported on linux", + }, + }, + }, + "multitarget": { + "libc": ["glibc", "default"], + }, + "jacoco" : "exclude", + }, + "com.oracle.svm.native.reporterchelper": { "subDir": "src", "native": "shared_lib", @@ -1724,6 +1746,32 @@ "jacoco" : "exclude", }, + "com.oracle.svm.native-image-utils-shim": { + "subDir": "src", + "sourceDirs": [ + "src", + "resources" + ], + "dependencies": [ + "com.oracle.svm.configure", + "com.oracle.svm.driver", + ], + "requiresConcealed" : { + "jdk.internal.vm.ci": [ + "jdk.vm.ci.meta", + ] + }, + "checkstyle": "com.oracle.svm.hosted", + "workingSets": "SVM", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "SVM_PROCESSOR", + ], + "javaCompliance" : "24+", + "spotbugs": "false", + "jacoco" : "exclude", + }, + "com.oracle.svm.truffle.tck" : { "subDir": "src", "sourceDirs": ["src"], @@ -1951,6 +1999,7 @@ com.oracle.svm.svm_enterprise, com.oracle.svm.svm_enterprise.llvm, com.oracle.svm_enterprise.ml_dataset, + org.graalvm.nativeimage.svm.niutils, org.graalvm.extraimage.builder, org.graalvm.extraimage.librarysupport, com.oracle.svm.extraimage_enterprise, @@ -2078,6 +2127,7 @@ org.graalvm.nativeimage.guest.staging, org.graalvm.nativeimage.junitsupport, org.graalvm.nativeimage.pointsto, + org.graalvm.nativeimage.svm.niutils, org.graalvm.truffle.runtime.svm""", ], "opens" : [], @@ -2395,6 +2445,7 @@ "dependency:com.oracle.svm.native.libchelper/*", "dependency:com.oracle.svm.native.jvm.posix/*", "dependency:com.oracle.svm.native.libcontainer/*", + "dependency:com.oracle.svm.native.extractsbom/*", "file:debug/include", "file:src/com.oracle.svm.core/src/com/oracle/svm/core/gc/shared/include", ], @@ -2518,6 +2569,41 @@ "maven": False, }, + "SVM_SBOM_EXTRACT": { + "subDir": "src", + "description" : "Native image utility for SBOM extraction", + "mainClass": "com.oracle.svm.niutils.NativeImageUtils", + "dependencies": [ + "com.oracle.svm.native-image-utils-shim", + ], + "distDependencies": [ + "LIBRARY_SUPPORT", + "SVM_DRIVER", + "compiler:GRAAL", + "sdk:NATIVEIMAGE", + "sdk:COLLECTIONS", + ], + "moduleInfo" : { + "name" : "org.graalvm.nativeimage.svm.niutils", + "exports" : [ + "com.oracle.svm.niutils", + ], + "requires": [ + "jdk.graal.compiler", + "org.graalvm.collections", + "org.graalvm.nativeimage.builder", + "org.graalvm.nativeimage.configure", + ], + "requiresConcealed" : { + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.meta", + ], + }, + }, + # vm: included as binary, tool descriptor intentionally not copied + "maven": False, + }, + "SVM_CONFIGURE": { "subDir": "src", "description" : "SubstrateVM native-image configuration tool", diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 1ced1d666187..69eba3eaa960 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -611,6 +611,10 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o @Option(help = "Enable detection and runtime container configuration support.")// public static final HostedOptionKey UseContainerSupport = new HostedOptionKey<>(true); + @LayerVerifiedOption(kind = Kind.Changed, severity = Severity.Error)// + @Option(help = "Enable linking with libextract_sbom.a")// + public static final HostedOptionKey UseLibExtractSbom = new HostedOptionKey<>(false); + @Option(help = "The size of each thread stack at run-time, in bytes.", type = OptionType.User)// public static final RuntimeOptionKey StackSize = new RuntimeOptionKey<>(0L); diff --git a/substratevm/src/com.oracle.svm.native-image-utils-shim/README.md b/substratevm/src/com.oracle.svm.native-image-utils-shim/README.md new file mode 100644 index 000000000000..a9a3cc51b0fe --- /dev/null +++ b/substratevm/src/com.oracle.svm.native-image-utils-shim/README.md @@ -0,0 +1,17 @@ +# Debugging native-image-utils with GDB + +The native-image-utils binary allows for extraction of compressed SBOM bytes +from another native image. + +Debugging native-image-utils requires building it with debug symbols. To do +that, execute: + +```bash +$ native-image -g --macro:native-image-utils-launcher +``` + +Then you can debug `native-image-utils` with GDB: + +```bash +$ gdb --args native-image-utils extract-sbom --image-path=/path/to/native-image-with-sbom-embedded +``` diff --git a/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/NativeImageUtils.java b/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/NativeImageUtils.java new file mode 100644 index 000000000000..1d56b88b5663 --- /dev/null +++ b/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/NativeImageUtils.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.niutils; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public class NativeImageUtils { + + private static String NAME = "native-image-utils"; + private static String EXTRACT_CMD = "extract-sbom"; + private static String EXECUTABLE_IMAGE_OPT = "image-path"; + + private static void usageAndExit() { + System.err.printf("Usage: %s %s --%s= opts = parseOptions(args); + String imagePath = opts.get(String.format("--%s", EXECUTABLE_IMAGE_OPT)); + if (imagePath == null) { + System.err.printf("--%s option missing", EXECUTABLE_IMAGE_OPT); + usageAndExit(); + } + exe = Path.of(imagePath); + if (!Files.exists(exe)) { + System.err.println("Image path does not exist or is not readable: " + exe); + usageAndExit(); + } + } catch (OptionParsingException e) { + System.err.println(e.getMessage()); + usageAndExit(); + } + System.exit(SbomExtractLibrary.extractSbom(exe)); + } + +} diff --git a/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/OptionParsingException.java b/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/OptionParsingException.java new file mode 100644 index 000000000000..ba3ae193089d --- /dev/null +++ b/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/OptionParsingException.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.niutils; + +@SuppressWarnings("serial") +public class OptionParsingException extends Exception { + + public OptionParsingException(String msg) { + super(msg); + } + +} diff --git a/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/SbomExtractLibrary.java b/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/SbomExtractLibrary.java new file mode 100644 index 000000000000..f968d90ef04c --- /dev/null +++ b/substratevm/src/com.oracle.svm.native-image-utils-shim/src/com/oracle/svm/niutils/SbomExtractLibrary.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.niutils; + +import java.nio.file.Path; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.function.CFunction; +import org.graalvm.nativeimage.c.function.CFunction.Transition; +import org.graalvm.nativeimage.c.function.CLibrary; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; + +import com.oracle.svm.core.SubstrateOptions; + +/** + * Provides Java-level access to the native {@code libextract_sbom} implementation. + */ +@CContext(SbomExtractLibraryDirectives.class) +@CLibrary(value = "extract_sbom", requireStatic = true) +public class SbomExtractLibrary { + + public static int extractSbom(Path executable) { + CCharPointerHolder exe = CTypeConversion.toCString(executable.toAbsolutePath().toString()); + return extractSbomNative(exe.get()); + } + + @CFunction(value = "extract_sbom", transition = Transition.NO_TRANSITION) + private static native int extractSbomNative(CCharPointer executable); + +} + +@Platforms(Platform.HOSTED_ONLY.class) +class SbomExtractLibraryDirectives implements CContext.Directives { + /** + * True if {@link SbomExtractLibrary} should be linked. + */ + @Override + public boolean isInConfiguration() { + return Platform.includedIn(Platform.LINUX.class) && SubstrateOptions.UseLibExtractSbom.getValue(); + } +} diff --git a/substratevm/src/com.oracle.svm.native.extractsbom/src/extract_sbom.c b/substratevm/src/com.oracle.svm.native.extractsbom/src/extract_sbom.c new file mode 100644 index 000000000000..0e771f2ac934 --- /dev/null +++ b/substratevm/src/com.oracle.svm.native.extractsbom/src/extract_sbom.c @@ -0,0 +1,322 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + const char *name; + GElf_Sym sym; + size_t section_index; +} elf_symbol; + +#ifdef DEBUG + +#define LOG_DEBUG(format, type, param) \ +{ \ + fprintf(stderr, format, (type)param); \ +} + +#else + +#define LOG_DEBUG(format, type, param) + +#endif + +static elf_symbol *read_symbols(Elf *elf, size_t *symcount) { + Elf_Scn *scn = NULL; + GElf_Shdr shdr; + Elf_Data *data; + Elf_Data *str_data; + elf_symbol *symbol_table = NULL; + size_t symbol_count = 0; + + // Find the .dynsym section + while ((scn = elf_nextscn(elf, scn)) != NULL) { + if (gelf_getshdr(scn, &shdr) != &shdr) { + continue; + } + + if (shdr.sh_type == SHT_DYNSYM) { + break; + } + } + + if (!scn) { + fprintf(stderr, "No dynamic symbol table found\n"); + return NULL; + } + + // Get the symbol table data + data = elf_getdata(scn, NULL); + if (!data) { + fprintf(stderr, "Failed to get symbol table data\n"); + return NULL; + } + + // Get the string table + Elf_Scn *str_scn = elf_getscn(elf, shdr.sh_link); + if (!str_scn) { + fprintf(stderr, "Failed to get string table section\n"); + return NULL; + } + + str_data = elf_getdata(str_scn, NULL); + if (!str_data) { + fprintf(stderr, "Failed to get string table data\n"); + return NULL; + } + + // Calculate number of symbols + symbol_count = shdr.sh_size / shdr.sh_entsize; + + if (symbol_count == 0) { + fprintf(stderr, "No dynamic symbols found\n"); + return NULL; + } + + // Allocate symbol table + symbol_table = (elf_symbol *) malloc(symbol_count * sizeof(elf_symbol)); + if (!symbol_table) { + fprintf(stderr, "Failed to allocate memory for symbol table\n"); + return NULL; + } + + // Read all symbols + for (size_t i = 0; i < symbol_count; i++) { + GElf_Sym sym; + if (gelf_getsym(data, i, &sym) != &sym) { + free(symbol_table); + fprintf(stderr, "Failed to read symbol at index %zu\n", i); + return NULL; + } + + symbol_table[i].sym = sym; + symbol_table[i].section_index = sym.st_shndx; + symbol_table[i].name = (const char *)str_data->d_buf + sym.st_name; + } + + *symcount = symbol_count; + return symbol_table; +} + +static elf_symbol *find_symbol(elf_symbol *symbol_table, size_t symcount, const char *name) { + for (size_t i = 0; i < symcount; i++) { + if (symbol_table[i].name && strcmp(symbol_table[i].name, name) == 0) { + return &symbol_table[i]; + } + } + return NULL; +} + +static int get_section_data_at_offset(Elf *elf, elf_symbol *sym, uint64_t offset, + size_t size, unsigned char **out_data) { + Elf_Scn *section; + GElf_Shdr shdr; + Elf_Data *section_data; + + section = elf_getscn(elf, sym->section_index); + if (!section || gelf_getshdr(section, &shdr) != &shdr) { + fprintf(stderr, "Failed to get section\n"); + return 1; + } + + section_data = elf_getdata(section, NULL); + if (!section_data) { + fprintf(stderr, "Failed to get section data\n"); + return 1; + } + + if (offset + size > section_data->d_size) { + fprintf(stderr, "Not enough data to read %zu bytes at offset %lu\n", size, offset); + return 1; + } + + *out_data = (unsigned char *)section_data->d_buf + offset; + return 0; +} + +#define CHUNK_SIZE 16384 + +// inflate data in 'source' with len 'source_len' to the buffer pointed to by +// 'dest'. The output length will be set in 'dest_len'. +static int inflate_gzip(const unsigned char *source, uint64_t source_len, + unsigned char **dest, uint64_t *dest_len) { + z_stream strm; + int ret; + size_t output_size = 0; + size_t output_capacity = CHUNK_SIZE; + unsigned char *output = malloc(output_capacity); + unsigned char out_buffer[CHUNK_SIZE]; + + if (!output) { + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + // Initialize zlib stream + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + // Initialize for gzip format (windowBits = 15 + 16 for gzip) + ret = inflateInit2(&strm, 15 + 16); + if (ret != Z_OK) { + fprintf(stderr, "Failed to initialize zlib: %d\n", ret); + free(output); + return 1; + } + + // Set input + strm.avail_in = source_len; + strm.next_in = (unsigned char *)source; + + // Decompress until end of stream + do { + strm.avail_out = CHUNK_SIZE; + strm.next_out = out_buffer; + + ret = inflate(&strm, Z_NO_FLUSH); + + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + fprintf(stderr, "Decompression error: %d\n", ret); + inflateEnd(&strm); + free(output); + return 1; + } + + size_t have = CHUNK_SIZE - strm.avail_out; + + // Resize output buffer if needed + if (output_size + have > output_capacity) { + output_capacity *= 2; + unsigned char *new_output = realloc(output, output_capacity); + if (!new_output) { + fprintf(stderr, "Failed to reallocate memory\n"); + inflateEnd(&strm); + free(output); + return 1; + } + output = new_output; + } + + // Copy decompressed data to output + memcpy(output + output_size, out_buffer, have); + output_size += have; + + } while (strm.avail_out == 0); + + // Cleanup + inflateEnd(&strm); + + if (ret != Z_STREAM_END) { + fprintf(stderr, "Incomplete decompression\n"); + free(output); + return 1; + } + + *dest = output; + *dest_len = output_size; + + return 0; +} + +#undef CHUNK_SIZE + +// Retrieves an embedded SBOM (gzip compressed) from a binary (executable or +// shared library). The executable parameter is the path to the binary. The +// SBOM is streamed to stdout +int extract_sbom(const char* executable) { + int fd; + Elf *elf; + elf_symbol *symbol_table = NULL; + elf_symbol *sbom_sym, *sbom_size_sym; + size_t symcount; + unsigned char *length_data, *compressed_data, *decompressed_data = NULL; + uint64_t sbom_length, sbom_offset, decompressed_len; + GElf_Shdr shdr; + int ret = 1; + + if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr, "Failed to initialize libelf: %s\n", elf_errmsg(-1)); + return 1; + } + + fd = open(executable, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Failed to open file '%s'\n", executable); + return 1; + } + + elf = elf_begin(fd, ELF_C_READ, NULL); + if (!elf || elf_kind(elf) != ELF_K_ELF) { + fprintf(stderr, "Failed to open ELF file '%s'\n", executable); + goto cleanup; + } + + symbol_table = read_symbols(elf, &symcount); + if (!symbol_table) { + goto cleanup; + } + + sbom_sym = find_symbol(symbol_table, symcount, "sbom"); + sbom_size_sym = find_symbol(symbol_table, symcount, "sbom_length"); + if (!sbom_sym || !sbom_size_sym) { + fprintf(stderr, "Required symbols 'sbom' or 'sbom_length' not found\n"); + goto cleanup; + } + + // Read sbom_length value (8 bytes) + Elf_Scn *size_section = elf_getscn(elf, sbom_size_sym->section_index); + if (!size_section || gelf_getshdr(size_section, &shdr) != &shdr) { + fprintf(stderr, "Failed to get sbom_length section\n"); + goto cleanup; + } + + uint64_t size_offset = sbom_size_sym->sym.st_value - shdr.sh_addr; + if (get_section_data_at_offset(elf, sbom_size_sym, size_offset, 8, &length_data) != 0) { + goto cleanup; + } + memcpy(&sbom_length, length_data, 8); + + // Read compressed SBOM data + Elf_Scn *sbom_section = elf_getscn(elf, sbom_sym->section_index); + if (!sbom_section || gelf_getshdr(sbom_section, &shdr) != &shdr) { + fprintf(stderr, "Failed to get sbom section\n"); + goto cleanup; + } + + sbom_offset = sbom_sym->sym.st_value - shdr.sh_addr; + if (get_section_data_at_offset(elf, sbom_sym, sbom_offset, sbom_length, &compressed_data) != 0) { + goto cleanup; + } + + LOG_DEBUG("sbom address: 0x%lx\n", unsigned long, sbom_sym->sym.st_value); + LOG_DEBUG("sbom_length: %lu bytes\n", unsigned long, sbom_length); + + // Decompress and output + if (inflate_gzip(compressed_data, sbom_length, &decompressed_data, &decompressed_len) != 0) { + fprintf(stderr, "Failed to decompress data\n"); + goto cleanup; + } + + fwrite(decompressed_data, decompressed_len, 1, stdout); + LOG_DEBUG("Successfully extracted %lu bytes of compressed data\n", unsigned long, sbom_length); + ret = 0; + +cleanup: + free(decompressed_data); + free(symbol_table); + if (elf) elf_end(elf); + if (fd >= 0) close(fd); + return ret; +} +