Skip to content

Commit

Permalink
pw_build_info: Add GNU build ID support
Browse files Browse the repository at this point in the history
Introduces the pw_build_info module, and adds support for reading GNU
build IDs from python and from inside the context of a C++ application.

Change-Id: I105627cffff28f5a6a2dbdf396be34143b0637d9
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/53760
Reviewed-by: Wyatt Hepler <[email protected]>
Reviewed-by: Ewout van Bekkum <[email protected]>
Commit-Queue: Armando Montanez <[email protected]>
  • Loading branch information
armandomontanez authored and CQ Bot Account committed Aug 5, 2021
1 parent 81017fe commit 6954a8d
Show file tree
Hide file tree
Showing 16 changed files with 605 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ group("module_docs") {
"$dir_pw_boot:docs",
"$dir_pw_boot_cortex_m:docs",
"$dir_pw_build:docs",
"$dir_pw_build_info:docs",
"$dir_pw_bytes:docs",
"$dir_pw_checksum:docs",
"$dir_pw_chrono:docs",
Expand Down
1 change: 1 addition & 0 deletions modules.gni
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ declare_args() {
dir_pw_boot = get_path_info("pw_boot", "abspath")
dir_pw_boot_cortex_m = get_path_info("pw_boot_cortex_m", "abspath")
dir_pw_build = get_path_info("pw_build", "abspath")
dir_pw_build_info = get_path_info("pw_build_info", "abspath")
dir_pw_bytes = get_path_info("pw_bytes", "abspath")
dir_pw_checksum = get_path_info("pw_checksum", "abspath")
dir_pw_chrono = get_path_info("pw_chrono", "abspath")
Expand Down
45 changes: 45 additions & 0 deletions pw_build_info/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2021 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

load(
"//pw_build:pigweed.bzl",
"pw_cc_library",
)

package(default_visibility = ["//visibility:public"])

licenses(["notice"])

pw_cc_library(
name = "build_id",
srcs = [
"build_id.cc",
],
hdrs = [
"public/pw_build_info/build_id.h",
],
includes = ["public"],
deps = [
"//pw_preprocessor",
],
)


# This is only used for the python tests.
filegroup(
name = "build_id_print_test",
srcs = [
"py/print_build_id.cc",
],
)
72 changes: 72 additions & 0 deletions pw_build_info/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2021 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

import("//build_overrides/pigweed.gni")

import("$dir_pw_build/target_types.gni")
import("$dir_pw_docgen/docs.gni")

config("linker_script") {
inputs = [ "build_id_linker_snippet.ld" ]

# Automatically add the gnu build ID linker sections when building for Linux.
# macOS and Windows executables are not supported, and embedded targets must
# manually add the snippet to their linker script in a read-only section.
if (current_os == "linux") {
# When building for Linux, the linker provides a default linker script.
# The add_build_id_to_default_script.ld wrapper includes the
# build_id_linker_snippet.ld script in a way that appends to the the
# default linker script instead of overriding it.
ldflags = [
"-T",
rebase_path("add_build_id_to_default_script.ld", root_build_dir),
]
lib_dirs = [ "." ]

inputs += [ "add_build_id_to_default_script.ld" ]
}
visibility = [ ":*" ]
}

config("gnu_build_id") {
ldflags = [ "-Wl,--build-id=sha1" ]
}

config("public_include_path") {
include_dirs = [ "public" ]
visibility = [ ":*" ]
}

# GNU build IDs aren't supported by Windows and macOS.
if (current_os != "mac" && current_os != "win") {
pw_source_set("build_id") {
all_dependent_configs = [
":gnu_build_id",
":linker_script",
]
public_configs = [ ":public_include_path" ]
cflags = [
"-Wno-array-bounds",
"-Wno-stringop-overflow",
]
public = [ "public/pw_build_info/build_id.h" ]
sources = [ "build_id.cc" ]
deps = [ dir_pw_preprocessor ]
}
}

pw_doc_group("docs") {
sources = [ "docs.rst" ]
inputs = [ "build_id_linker_snippet.ld" ]
}
30 changes: 30 additions & 0 deletions pw_build_info/add_build_id_to_default_linker_script.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021 The Pigweed Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

SECTIONS
{
.note.gnu.build-id :
{
INCLUDE build_id_linker_snippet.ld
}
}

/*
* The INSERT directive instructs the linker to append the directives in this
* script to the default linker script, rather than replace the default with
* this script. The build ID is read only, so place it just after .rodata.
*/
INSERT AFTER .rodata
49 changes: 49 additions & 0 deletions pw_build_info/build_id.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_build_info/build_id.h"

#include <cstdint>
#include <cstring>
#include <span>

#include "pw_preprocessor/compiler.h"

extern "C" const uint8_t gnu_build_id_begin;

namespace pw::build_info {
namespace {

PW_PACKED(struct) ElfNoteInfo {
uint32_t name_size;
uint32_t descriptor_size;
uint32_t type;
};

} // namespace

std::span<const std::byte> BuildId() {
// Read the sizes at the beginning of the note section.
ElfNoteInfo build_id_note_sizes;
memcpy(
&build_id_note_sizes, &gnu_build_id_begin, sizeof(build_id_note_sizes));
// Skip the "name" entry of the note section, and return a span to the
// descriptor.
return std::as_bytes(std::span(&gnu_build_id_begin +
sizeof(build_id_note_sizes) +
build_id_note_sizes.name_size,
build_id_note_sizes.descriptor_size));
}

} // namespace pw::build_info
22 changes: 22 additions & 0 deletions pw_build_info/build_id_linker_snippet.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2021 The Pigweed Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

/* Include this linker snippet in a section of your linker script that specifies
* where .rodata or .text will live in flash.
*/
. = ALIGN(4);
gnu_build_id_begin = .;
*(.note.gnu.build-id);
54 changes: 54 additions & 0 deletions pw_build_info/docs.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.. _module-pw_build_info:

-------------
pw_build_info
-------------

.. warning::
This module is under construction and may not be ready for use.

pw_build_info provides tooling, build integration, and libraries for generating,
embedding, and parsing build-related information that is embedded into
binaries. Simple numeric version numbering doesn't typically express things
like where the binary originated, what devices it's compatible with, whether
local changes were present when the binary was built, and more. pw_build_info
simplifies the process of integrating rich version metadata to answer more
complex questions about compiled binaries.

GNU Build IDs
=============
This module provides C++ and python libraries for reading GNU build IDs
generated by the link step of a C++ executable. These build IDs are essentially
hashes of the final linked binary, meaning two identical binaries will have
identical build IDs. This can be used to accurately identify matching
binaries.

Linux executables that depend on the ``build_id`` GN target will automatically
generate GNU build IDs. Windows and macOS binaries cannot use this target as
the implementation of GNU build IDs depends on the ELF file format.

Embedded targets must first explicitly place the GNU build ID section into a
non-info section of their linker script that is readable by the firmware. The
following linker snippet may be copied into a read-only section (just like the
.rodata or .text sections):

.. literalinclude:: build_id_linker_snippet.ld

This snippet may be placed directly into an existing section, as it is not
required to live in its own dedicated section. When opting to create a
dedicated section for the build ID to reside in, Pigweed recommends naming the
section ``.note.gnu.build-id`` as it makes it slightly easier for tools to
parse the build ID out of a binary. After the linker script has been properly
set up, the ``build_id`` GN target may be used to read the build ID at
runtime.

Python tooling
--------------
GNU build IDs can be parsed out of ELF files using the ``build_id`` python tool.
Simply point the tool to a binary with a GNU build ID and the build ID will be
printed out if it is found.

.. code-block:: sh
$ python -m pw_build_info.build_id my_device_image.elf
d43cce74f18522052f77a1fa3fb7a25fe33f40dd
30 changes: 30 additions & 0 deletions pw_build_info/public/pw_build_info/build_id.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include <cstddef>
#include <span>

namespace pw::build_info {

// Build IDs may be generated with several different algorithms. The largest of
// these (aside from user-provided build IDs) are a fixed size of 20 bytes.
inline constexpr size_t kMaxBuildIdSizeBytes = 20;

// Reads a GNU build ID from the address starting at the address of the
// `gnu_build_id_begin` symbol. This must be manually explicitly provided as
// part of a linker script. See build_id_linker_snippet.ld for an example.
std::span<const std::byte> BuildId();

} // namespace pw::build_info
39 changes: 39 additions & 0 deletions pw_build_info/py/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2021 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

import("//build_overrides/pigweed.gni")

import("$dir_pw_build/python.gni")

pw_python_package("py") {
generate_setup = {
name = "pw_build_info"
version = "0.0.1"
install_requires = [ "pyelftools" ]
}
inputs = [ "print_build_id.cc" ]
sources = [
"pw_build_info/__init__.py",
"pw_build_info/build_id.py",
]

# This test will only ever work on Linux as it requires the ability to compile
# AND run an ELF file.
if (host_os == "linux") {
tests = [ "build_id_test.py" ]
python_test_deps = [ "$dir_pw_cli/py" ]
}

pylintrc = "$dir_pigweed/.pylintrc"
}
Loading

0 comments on commit 6954a8d

Please sign in to comment.