aboutsummaryrefslogtreecommitdiffhomepage
path: root/ldd
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-11-14 21:38:35 +0900
committerOphestra <cat@gensokyo.uk>2025-11-14 21:38:35 +0900
commit42759e7a9f4e6c45d2e043a404e444160e6cddba (patch)
tree0485fd954967766cc97f1c97cbbc44814752be3b /ldd
parent8e2d2c8246409dca96cee8589c0d841ef878d802 (diff)
ldd: create musl entry representation
This mostly helps with debugging. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'ldd')
-rw-r--r--ldd/ldd.go32
-rw-r--r--ldd/ldd_test.go83
2 files changed, 115 insertions, 0 deletions
diff --git a/ldd/ldd.go b/ldd/ldd.go
index 7b88e698..9c892d7a 100644
--- a/ldd/ldd.go
+++ b/ldd/ldd.go
@@ -13,6 +13,7 @@ import (
"fmt"
"io"
"strconv"
+ "strings"
"hakurei.app/container/check"
)
@@ -117,6 +118,37 @@ func (e *Entry) UnmarshalText(data []byte) error {
return e.decodeLocationSegment(segments[iL])
}
+// String returns the musl ldd(1) representation of [Entry].
+func (e *Entry) String() string {
+ // nameInvalid is used for a zero-length e.Name
+ const nameInvalid = "invalid"
+
+ var buf strings.Builder
+
+ // libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
+ l := len(e.Name) + 1
+ if e.Name == "" {
+ l += len(nameInvalid)
+ }
+ if e.Path != nil {
+ l += len(entrySegmentFullSeparator) + 1 + len(e.Path.String()) + 1
+ }
+ l += len(entrySegmentLocationPrefix) + 1<<4 + 1
+ buf.Grow(l)
+
+ if e.Name != "" {
+ buf.WriteString(e.Name + " ")
+ } else {
+ buf.WriteString(nameInvalid + " ")
+ }
+ if e.Path != nil {
+ buf.WriteString(entrySegmentFullSeparator + " " + e.Path.String() + " ")
+ }
+ buf.WriteString(entrySegmentLocationPrefix + strconv.FormatUint(e.Location, 16) + string(entrySegmentLocationSuffix))
+
+ return buf.String()
+}
+
// Path returns a deduplicated slice of absolute directory paths in entries.
func Path(entries []*Entry) []*check.Absolute {
p := make([]*check.Absolute, 0, len(entries)*2)
diff --git a/ldd/ldd_test.go b/ldd/ldd_test.go
index 2bacb7aa..a994d330 100644
--- a/ldd/ldd_test.go
+++ b/ldd/ldd_test.go
@@ -158,6 +158,89 @@ libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`, []*ldd.Entr
}
}
+func TestString(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ e ldd.Entry
+ want string
+ }{
+ {"ld", ldd.Entry{
+ Name: "/lib/ld-musl-x86_64.so.1",
+ Location: 0x7ff71c0a4000,
+ }, `/lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
+
+ {"libzstd", ldd.Entry{
+ Name: "libzstd.so.1",
+ Path: check.MustAbs("/usr/lib/libzstd.so.1"),
+ Location: 0x7ff71bfd2000,
+ }, `libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)`},
+
+ {"liblzma", ldd.Entry{
+ Name: "liblzma.so.5",
+ Path: check.MustAbs("/usr/lib/liblzma.so.5"),
+ Location: 0x7ff71bf9a000,
+ }, `liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7ff71bf9a000)`},
+
+ {"libz", ldd.Entry{
+ Name: "libz.so.1",
+ Path: check.MustAbs("/lib/libz.so.1"),
+ Location: 0x7ff71bf80000,
+ }, `libz.so.1 => /lib/libz.so.1 (0x7ff71bf80000)`},
+
+ {"libcrypto", ldd.Entry{
+ Name: "libcrypto.so.3",
+ Path: check.MustAbs("/lib/libcrypto.so.3"),
+ Location: 0x7ff71ba00000,
+ }, `libcrypto.so.3 => /lib/libcrypto.so.3 (0x7ff71ba00000)`},
+
+ {"libc", ldd.Entry{
+ Name: "libc.musl-x86_64.so.1",
+ Path: check.MustAbs("/lib/ld-musl-x86_64.so.1"),
+ Location: 0x7ff71c0a4000,
+ }, `libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
+
+ {"invalid", ldd.Entry{
+ Location: 0x7ff71c0a4000,
+ }, `invalid (0x7ff71c0a4000)`},
+
+ {"invalid long", ldd.Entry{
+ Path: check.MustAbs("/lib/ld-musl-x86_64.so.1"),
+ Location: 0x7ff71c0a4000,
+ }, `invalid => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ t.Run("decode", func(t *testing.T) {
+ if tc.e.Name == "" {
+ return
+ }
+ t.Parallel()
+
+ var got ldd.Entry
+ if err := got.UnmarshalText([]byte(tc.want)); err != nil {
+ t.Fatalf("UnmarshalText: error = %v", err)
+ }
+
+ if !reflect.DeepEqual(&got, &tc.e) {
+ t.Errorf("UnmarshalText: %#v, want %#v", got, tc.e)
+ }
+ })
+
+ t.Run("encode", func(t *testing.T) {
+ t.Parallel()
+
+ if got := tc.e.String(); got != tc.want {
+ t.Errorf("String: %s, want %s", got, tc.want)
+ }
+ })
+ })
+ }
+}
+
// mustMarshalJSON calls [json.Marshal] and returns the resulting data.
func mustMarshalJSON(v any) []byte {
if data, err := json.Marshal(v); err != nil {