diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 38fff796af..d48690ba10 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -90,6 +90,7 @@ data/typeinfo/golang/golang_1.17_anybit_any.gdt||GHIDRA||||END| data/typeinfo/golang/golang_1.18_anybit_any.gdt||GHIDRA||||END| data/typeinfo/golang/golang_1.19_anybit_any.gdt||GHIDRA||||END| data/typeinfo/golang/golang_1.20_anybit_any.gdt||GHIDRA||||END| +data/typeinfo/golang/runtimesnapshot.go||GHIDRA||||END| data/typeinfo/mac_10.9/mac_osx.gdt||GHIDRA||||END| data/typeinfo/win32/msvcrt/clsids.txt||GHIDRA||reviewed||END| data/typeinfo/win32/msvcrt/guids.txt||GHIDRA||reviewed||END| diff --git a/Ghidra/Features/Base/data/GolangFunctionsThatDoNotReturn b/Ghidra/Features/Base/data/GolangFunctionsThatDoNotReturn index 1c85fca0c5..a099b15bce 100644 --- a/Ghidra/Features/Base/data/GolangFunctionsThatDoNotReturn +++ b/Ghidra/Features/Base/data/GolangFunctionsThatDoNotReturn @@ -6,55 +6,104 @@ runtime.exitThread runtime.fatal runtime.fatalthrow runtime.fatalpanic -runtime.gopanic -runtime.panicdivide runtime.throw +runtime.gopanic +runtime.goPanicExtendIndex +runtime.goPanicExtendIndexU +runtime.goPanicExtendSlice3Acap +runtime.goPanicExtendSlice3AcapU +runtime.goPanicExtendSlice3Alen +runtime.goPanicExtendSlice3AlenU +runtime.goPanicExtendSlice3B +runtime.goPanicExtendSlice3BU +runtime.goPanicExtendSlice3C +runtime.goPanicExtendSlice3CU +runtime.goPanicExtendSliceAcap +runtime.goPanicExtendSliceAcapU +runtime.goPanicExtendSliceAlen +runtime.goPanicExtendSliceAlenU +runtime.goPanicExtendSliceB +runtime.goPanicExtendSliceBU runtime.goPanicIndex runtime.goPanicIndexU -runtime.goPanicSliceAlen -runtime.goPanicSliceAlenU -runtime.goPanicSliceAcap -runtime.goPanicSliceAcapU -runtime.goPanicSliceB -runtime.goPanicSliceBU -runtime.goPanicSlice3Alen -runtime.goPanicSlice3AlenU runtime.goPanicSlice3Acap runtime.goPanicSlice3AcapU +runtime.goPanicSlice3Alen +runtime.goPanicSlice3AlenU runtime.goPanicSlice3B runtime.goPanicSlice3BU runtime.goPanicSlice3C runtime.goPanicSlice3CU +runtime.goPanicSliceAcap +runtime.goPanicSliceAcapU +runtime.goPanicSliceAlen +runtime.goPanicSliceAlenU +runtime.goPanicSliceB +runtime.goPanicSliceBU runtime.goPanicSliceConvert +runtime.panic +runtime.panicCheck1 +runtime.panicCheck2 +runtime.panicdivide +runtime.panicdottypeE +runtime.panicdottypeI +runtime.panicExtendIndex +runtime.panicExtendIndexU +runtime.panicExtendSlice3Acap +runtime.panicExtendSlice3AcapU +runtime.panicExtendSlice3Alen +runtime.panicExtendSlice3AlenU +runtime.panicExtendSlice3B +runtime.panicExtendSlice3BU +runtime.panicExtendSlice3C +runtime.panicExtendSlice3CU +runtime.panicExtendSliceAcap +runtime.panicExtendSliceAcapU +runtime.panicExtendSliceAlen +runtime.panicExtendSliceAlenU +runtime.panicExtendSliceB +runtime.panicExtendSliceBU +runtime.panicfloat +runtime.panicIndex runtime.panicIndex runtime.panicIndexU -runtime.panicSliceAlen -runtime.panicSliceAlenU -runtime.panicSliceAcap -runtime.panicSliceAcapU -runtime.panicSliceB -runtime.panicSliceBU -runtime.panicSlice3Alen -runtime.panicSlice3AlenU +runtime.panicmakeslicecap +runtime.panicmakeslicelen +runtime.panicmem +runtime.panicmemAddr +runtime.panicnildottype +runtime.panicoverflow +runtime.panicshift +runtime.panicSlice3Acap runtime.panicSlice3Acap runtime.panicSlice3AcapU +runtime.panicSlice3Alen +runtime.panicSlice3Alen +runtime.panicSlice3AlenU runtime.panicSlice3B runtime.panicSlice3BU runtime.panicSlice3C runtime.panicSlice3CU +runtime.panicSliceAcap +runtime.panicSliceAcapU +runtime.panicSliceAcapU +runtime.panicSliceAlen +runtime.panicSliceAlenU +runtime.panicSliceAlenU +runtime.panicSliceB +runtime.panicSliceBU runtime.panicSliceConvert +runtime.panicUnaligned +runtime.panicunsafeslicelen +runtime.panicunsafeslicelen1 +runtime.panicunsafeslicenilptr +runtime.panicunsafeslicenilptr1 +runtime.panicunsafestringlen +runtime.panicunsafestringnilptr +runtime.panicwrap -runtime.panicdottypeE -runtime.panicdottypeI -runtime.panicnildottype - -runtime.panicoverflow -runtime.panicfloat -runtime.panicmem -runtime.panicmemAddr -runtime.panicshift runtime.goexit0 runtime.goexit0.abi0 diff --git a/Ghidra/Features/Base/data/typeinfo/golang/runtimesnapshot.go b/Ghidra/Features/Base/data/typeinfo/golang/runtimesnapshot.go new file mode 100644 index 0000000000..85de94a012 --- /dev/null +++ b/Ghidra/Features/Base/data/typeinfo/golang/runtimesnapshot.go @@ -0,0 +1,2299 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ + +/* + This file attempts to touch all functions in all built-in golang modules so + that the resulting binary will contain a full snapshot, via DWARF, of all + golang functions and their parameter information. + Care must be taken to ensure that invalid parameter arguments do not cause a + static exception that allows the golang compiler to elide the call and + portions of the caller function. +*/ + +package main + +import ( + "archive/tar" + "archive/zip" + "bufio" + "bytes" + "compress/bzip2" + "compress/flate" + "compress/gzip" + "compress/lzw" + "compress/zlib" + "container/heap" + "container/list" + "container/ring" + "context" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/dsa" + "crypto/ecdh" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/hmac" + "crypto/md5" + "crypto/rand" + "crypto/rc4" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "crypto/subtle" + "crypto/tls" + "crypto/x509" + "database/sql" + "debug/buildinfo" + "debug/elf" + "debug/gosym" + "errors" + "flag" + "fmt" + "io" + "io/fs" + "io/ioutil" + "log" + //"log/syslog" // needs to be commented out for windows builds + "math" + "math/big" + mathrand "math/rand" + "net" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "plugin" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "text/scanner" + "text/tabwriter" + "text/template" + "time" + "unicode" + "unicode/utf16" + "unicode/utf8" + //"unsafe" // does not produce function signatures that can be captured +) + +//go:noinline +func archivePackage() { + archiveTarPackage() + archiveZipPackage() +} + +//go:noinline +func archiveTarPackage() { + header, _ := tar.FileInfoHeader(nil, "link") + header.FileInfo() + + reader := tar.NewReader(nil) + reader.Next() + + b := make([]byte, 100) + reader.Read(nil) + + writer := tar.NewWriter(nil) + writer.Close() + writer.Flush() + writer.Write(b) + writer.WriteHeader(nil) + + fmt.Println("end of archive/tar") +} + +//go:noinline +func archiveZipPackage() { + zip.RegisterCompressor(0, nil) + zip.RegisterDecompressor(0, nil) + + var file *zip.File + file.DataOffset() + file.Open() + file.OpenRaw() + + fh, _ := zip.FileInfoHeader(nil) + + fh.FileInfo() + fh.ModTime() + fh.Mode() + fh.SetModTime(time.Now()) + fh.SetMode(0) + + rc, _ := zip.OpenReader("name") + rc.Close() + + reader, _ := zip.NewReader(nil, 0) + reader.Open("name") + reader.RegisterDecompressor(0, nil) + + writer := zip.NewWriter(nil) + writer.Close() + writer.Copy(nil) + writer.Create("name") + writer.CreateHeader(nil) + writer.CreateRaw(nil) + writer.Flush() + writer.RegisterCompressor(0, nil) + writer.SetComment("string") + writer.SetOffset(0) + + fmt.Println("end of archive/zip") +} + +//go:noinline +func bufioPackage() { + b := make([]byte, 100) + + bufio.ScanBytes(nil, true) + bufio.ScanLines(nil, true) + bufio.ScanRunes(nil, true) + bufio.ScanWords(nil, true) + bufio.NewReadWriter(nil, nil) + reader := bufio.NewReader(nil) + bufio.NewReaderSize(nil, 0) + reader.Buffered() + reader.Discard(0) + reader.Peek(0) + reader.Read(b) + reader.ReadByte() + reader.ReadBytes(0) + reader.ReadLine() + reader.ReadRune() + reader.ReadSlice(0) + reader.ReadString(0) + reader.Reset(nil) + reader.Size() + reader.UnreadByte() + reader.UnreadRune() + reader.WriteTo(nil) + + scanner := bufio.NewScanner(nil) + scanner.Buffer(nil, 0) + scanner.Bytes() + scanner.Err() + scanner.Scan() + scanner.Split(nil) + scanner.Text() + + writer := bufio.NewWriter(nil) + bufio.NewWriterSize(nil, 0) + writer.Available() + writer.AvailableBuffer() + writer.Buffered() + writer.Flush() + writer.ReadFrom(nil) + writer.Reset(nil) + writer.Size() + writer.Write(nil) + writer.WriteByte(0) + writer.WriteRune(0) + writer.WriteString("string") + + fmt.Println("end of bufio") +} + +//go:noinline +func bytesPackage() { + bytes.Clone(nil) + bytes.Compare(nil, nil) + bytes.Contains(nil, nil) + bytes.ContainsAny(nil, "chars") + bytes.ContainsRune(nil, 0) + bytes.Count(nil, nil) + bytes.Cut(nil, nil) + bytes.CutPrefix(nil, nil) + bytes.CutSuffix(nil, nil) + bytes.Equal(nil, nil) + bytes.EqualFold(nil, nil) + bytes.Fields(nil) + bytes.FieldsFunc(nil, nil) + bytes.HasPrefix(nil, nil) + bytes.HasSuffix(nil, nil) + bytes.Index(nil, nil) + bytes.IndexAny(nil, "chars") + bytes.IndexByte(nil, 0) + bytes.IndexFunc(nil, nil) + bytes.IndexRune(nil, 0) + bytes.Join(nil, nil) + bytes.LastIndex(nil, nil) + bytes.LastIndexAny(nil, "chars") + bytes.LastIndexByte(nil, 0) + bytes.LastIndexFunc(nil, nil) + bytes.Map(nil, nil) + bytes.Repeat(nil, 0) + bytes.Replace(nil, nil, nil, 0) + bytes.ReplaceAll(nil, nil, nil) + bytes.Runes(nil) + bytes.Split(nil, nil) + bytes.SplitAfter(nil, nil) + bytes.SplitAfterN(nil, nil, 0) + bytes.SplitN(nil, nil, 0) + bytes.Title(nil) + bytes.ToLower(nil) + bytes.ToLowerSpecial(nil, nil) + bytes.ToTitle(nil) + bytes.ToTitleSpecial(nil, nil) + bytes.ToUpper(nil) + bytes.ToUpperSpecial(nil, nil) + bytes.ToValidUTF8(nil, nil) + bytes.Trim(nil, "string") + bytes.TrimFunc(nil, nil) + bytes.TrimLeft(nil, "string") + bytes.TrimLeftFunc(nil, nil) + bytes.TrimPrefix(nil, nil) + bytes.TrimRight(nil, "string") + bytes.TrimRightFunc(nil, nil) + bytes.TrimSpace(nil) + bytes.TrimSuffix(nil, nil) + buf := bytes.NewBuffer(nil) + bytes.NewBufferString("string") + buf.Bytes() + buf.Cap() + buf.Grow(0) + buf.Len() + buf.Next(0) + buf.Read(nil) + buf.ReadByte() + buf.ReadBytes(0) + buf.ReadFrom(nil) + buf.ReadRune() + buf.ReadString(0) + buf.Reset() + buf.String() + buf.Truncate(0) + buf.UnreadByte() + buf.UnreadRune() + buf.Write(nil) + buf.WriteByte(0) + buf.WriteRune(0) + buf.WriteString("string") + buf.WriteTo(nil) + reader := bytes.NewReader(nil) + reader.Len() + reader.Read(nil) + reader.ReadAt(nil, 0) + reader.ReadByte() + reader.ReadRune() + reader.Reset(nil) + reader.Seek(0, 0) + reader.Size() + reader.UnreadByte() + reader.UnreadRune() + reader.WriteTo(nil) + + fmt.Println("end of bytes") +} + +//go:noinline +func compressPackage() { + compressGzipPackage() + compressBzip2Package() + compressFlatePackage() + compressLzwPackage() + compressZlibPackage() +} + +//go:noinline +func compressGzipPackage() { + reader, _ := gzip.NewReader(nil) + reader.Close() + reader.Multistream(true) + reader.Read(nil) + reader.Reset(nil) + + writer := gzip.NewWriter(nil) + gzip.NewWriterLevel(nil, 0) + writer.Close() + writer.Flush() + writer.Reset(nil) + writer.Write(nil) + + fmt.Println("end of compress/gzip") +} + +//go:noinline +func compressBzip2Package() { + bzip2.NewReader(nil) + + fmt.Println("end of compress/bzip2") +} + +//go:noinline +func compressFlatePackage() { + flate.NewReader(nil) + flate.NewReaderDict(nil, nil) + writer, _ := flate.NewWriter(nil, 0) + flate.NewWriterDict(nil, 0, nil) + writer.Close() + writer.Flush() + writer.Reset(nil) + writer.Write(nil) + + fmt.Println("end of compress/flate") +} + +//go:noinline +func compressLzwPackage() { + reader := lzw.NewReader(nil, 0, 0) + writer := lzw.NewWriter(nil, 0, 0) + + reader.Close() + reader.Read(nil) + var r *lzw.Reader + r.Reset(nil, 0, 0) + + writer.Close() + var w *lzw.Writer + w.Reset(nil, 0, 0) + writer.Write(nil) + + fmt.Println("end of compress/lzw") +} + +//go:noinline +func compressZlibPackage() { + zlib.NewReader(nil) + zlib.NewReaderDict(nil, nil) + + writer := zlib.NewWriter(nil) + zlib.NewWriterLevel(nil, 0) + zlib.NewWriterLevelDict(nil, 0, nil) + writer.Close() + writer.Flush() + writer.Reset(nil) + writer.Write(nil) + + fmt.Println("end of compress/zlib") +} + +//go:noinline +func containerPackage() { + containerHeapPackage() + containerListPackage() + containerRingPackage() +} + +//go:noinline +func containerHeapPackage() { + heap.Fix(nil, 0) + heap.Init(nil) + heap.Pop(nil) + heap.Push(nil, 0) + heap.Remove(nil, 0) + + fmt.Println("end of container/heap") +} + +//go:noinline +func containerListPackage() { + l := list.New() + l.Back() + l.Front() + l.Init() + l.InsertAfter(nil, nil) + l.InsertBefore(nil, nil) + l.Len() + l.MoveAfter(nil, nil) + l.MoveBefore(nil, nil) + l.MoveToBack(nil) + l.MoveToFront(nil) + l.PushBack(nil) + l.PushBackList(nil) + l.PushFront(nil) + l.PushFrontList(nil) + l.Remove(nil) + + fmt.Println("end of container/list") +} + +//go:noinline +func containerRingPackage() { + r := ring.New(0) + r.Do(nil) + r.Len() + r.Link(nil) + r.Move(0) + r.Next() + r.Prev() + r.Unlink(0) + + fmt.Println("end of container/ring") +} + +//go:noinline +func contextPackage() { + c := context.Background() + context.TODO() + context.WithValue(c, nil, nil) + + context.Cause(c) + context.WithCancel(c) + context.WithCancelCause(c) + context.WithDeadline(c, time.Now()) + context.WithTimeout(c, 0) + + fmt.Println("end of context") +} + +//go:noinline +func cryptoPackage() { + //crypto.RegisterHash(0, nil) + var h crypto.Hash = 1 + h.Available() + h.HashFunc() + h.New() + h.Size() + h.String() + + cryptoAesPackage() + cryptoCipherPackage() + cryptoDesPackage() + cryptoDsaPackage() + cryptoEcdhPackage() + cryptoEcdsaPackage() + cryptoEd25519Package() + cryptoEllipticPackage() + cryptoHmacPackage() + cryptoMd5Package() + cryptoRandPackage() + cryptoRc4Package() + cryptoRsaPackage() + cryptoSha1Package() + cryptoSha256Package() + cryptoSha512Package() + cryptoSubtlePackage() + cryptoTlsPackage() + cryptoX509Package() + + fmt.Println("end of crypto package") +} + +//go:noinline +func cryptoAesPackage() { + aes.NewCipher(nil) + fmt.Println("end of crypto/aes") +} + +//go:noinline +func cryptoCipherPackage() { + cipher.NewGCM(nil) + cipher.NewGCMWithNonceSize(nil, 0) + cipher.NewGCMWithTagSize(nil, 0) + + cipher.NewCBCDecrypter(nil, nil) + cipher.NewCBCEncrypter(nil, nil) + + cipher.NewCFBDecrypter(nil, nil) + cipher.NewCFBEncrypter(nil, nil) + cipher.NewCTR(nil, nil) + cipher.NewOFB(nil, nil) + + var sr cipher.StreamReader + sr.Read(nil) + var sw cipher.StreamWriter + sw.Close() + sw.Write(nil) + fmt.Println("end of crypto/cipher") +} + +//go:noinline +func cryptoDesPackage() { + des.NewCipher(nil) + des.NewTripleDESCipher(nil) + fmt.Println("end of crypto/des") +} + +//go:noinline +func cryptoDsaPackage() { + dsa.GenerateKey(nil, nil) + dsa.GenerateParameters(nil, nil, 0) + dsa.Sign(nil, nil, nil) + dsa.Verify(nil, nil, nil, nil) + fmt.Println("end of crypto/dsa") +} + +//go:noinline +func cryptoEcdhPackage() { + ecdh.P256() + ecdh.P384() + ecdh.P521() + ecdh.X25519() + + var pk *ecdh.PrivateKey + pk.Bytes() + pk.ECDH(nil) + pk.Equal(nil) + pk.Public() + pk.PublicKey() + + var pubk *ecdh.PublicKey + pubk.Bytes() + pubk.Curve() + pubk.Equal(nil) + fmt.Println("end of crypto/ecdh") +} + +//go:noinline +func cryptoEcdsaPackage() { + ecdsa.Sign(nil, nil, nil) + ecdsa.SignASN1(nil, nil, nil) + ecdsa.Verify(nil, nil, nil, nil) + ecdsa.VerifyASN1(nil, nil, nil) + + var pk2 *ecdsa.PrivateKey + ecdsa.GenerateKey(nil, nil) + pk2.ECDH() + pk2.Equal(nil) + pk2.Public() + pk2.Sign(nil, nil, nil) + + var pubk2 *ecdsa.PublicKey + pubk2.ECDH() + pubk2.Equal(nil) + fmt.Println("end of crypto/ecdsa") +} + +//go:noinline +func cryptoEd25519Package() { + + ed25519.GenerateKey(nil) + ed25519.Sign(nil, nil) + ed25519.Verify(nil, nil, nil) + ed25519.VerifyWithOptions(nil, nil, nil, nil) + pk3 := ed25519.NewKeyFromSeed(nil) + pk3.Equal(nil) + pk3.Public() + pk3.Seed() + pk3.Sign(nil, nil, nil) + + var pubk3 *ed25519.PublicKey + pubk3.Equal(nil) + fmt.Println("end of crypto/ed25519") +} + +//go:noinline +func cryptoEllipticPackage() { + elliptic.GenerateKey(nil, nil) + elliptic.Marshal(nil, nil, nil) + elliptic.MarshalCompressed(nil, nil, nil) + elliptic.Unmarshal(nil, nil) + elliptic.UnmarshalCompressed(nil, nil) + elliptic.P224() + elliptic.P256() + elliptic.P384() + elliptic.P521() + + var cp *elliptic.CurveParams + cp.Add(nil, nil, nil, nil) + cp.Double(nil, nil) + cp.IsOnCurve(nil, nil) + cp.Params() + cp.ScalarBaseMult(nil) + cp.ScalarMult(nil, nil, nil) + fmt.Println("end of crypto/elliptic") +} + +//go:noinline +func cryptoHmacPackage() { + hmac.Equal(nil, nil) + hmac.New(nil, nil) + fmt.Println("end of crypto/hmac") +} + +//go:noinline +func cryptoMd5Package() { + md5.New() + md5.Sum(nil) + fmt.Println("end of crypto/md5") +} + +//go:noinline +func cryptoRandPackage() { + rand.Int(nil, nil) + rand.Prime(nil, 0) + rand.Read(nil) + fmt.Println("end of crypto/rand") +} + +//go:noinline +func cryptoRc4Package() { + rc4cipher, _ := rc4.NewCipher(nil) + rc4cipher.Reset() + rc4cipher.XORKeyStream(nil, nil) + fmt.Println("end of crypto/rc4") +} + +//go:noinline +func cryptoRsaPackage() { + + rsa.DecryptOAEP(nil, nil, nil, nil, nil) + rsa.DecryptPKCS1v15(nil, nil, nil) + rsa.DecryptPKCS1v15SessionKey(nil, nil, nil, nil) + rsa.EncryptOAEP(nil, nil, nil, nil, nil) + rsa.EncryptPKCS1v15(nil, nil, nil) + rsa.SignPKCS1v15(nil, nil, 0, nil) + rsa.SignPSS(nil, nil, 0, nil, nil) + rsa.VerifyPKCS1v15(nil, 0, nil, nil) + rsa.VerifyPSS(nil, 0, nil, nil, nil) + + rsapk, _ := rsa.GenerateKey(nil, 0) + rsa.GenerateMultiPrimeKey(nil, 0, 0) + + rsapk.Decrypt(nil, nil, nil) + rsapk.Equal(nil) + rsapk.Precompute() + rsapk.Public() + rsapk.Sign(nil, nil, nil) + rsapk.Validate() + + var rsapub rsa.PublicKey + rsapub.Equal(nil) + rsapub.Size() + fmt.Println("end of crypto/rsa") +} + +//go:noinline +func cryptoSha1Package() { + sha1.New() + sha1.Sum(nil) + + fmt.Println("end of crypto/sha1") +} + +//go:noinline +func cryptoSha256Package() { + sha256.New() + sha256.New224() + sha256.Sum224(nil) + sha256.Sum256(nil) + + fmt.Println("end of crypto/sha256") +} + +//go:noinline +func cryptoSha512Package() { + sha512.New() + sha512.New384() + sha512.New512_224() + sha512.New512_256() + sha512.Sum384(nil) + sha512.Sum512(nil) + sha512.Sum512_224(nil) + sha512.Sum512_256(nil) + + fmt.Println("end of crypto/sha512") +} + +//go:noinline +func cryptoSubtlePackage() { + subtle.ConstantTimeByteEq(0, 0) + subtle.ConstantTimeCompare(nil, nil) + subtle.ConstantTimeCopy(0, nil, nil) + subtle.ConstantTimeEq(0, 0) + subtle.ConstantTimeLessOrEq(0, 0) + subtle.ConstantTimeSelect(0, 0, 0) + subtle.XORBytes(nil, nil, nil) + + fmt.Println("end of crypto/subtle") +} + +//go:noinline +func cryptoTlsPackage() { + tls.CipherSuiteName(0) + tls.Listen("network", "laddr", nil) + tls.NewListener(nil, nil) + tls.LoadX509KeyPair("certfile", "keyfile") + tls.X509KeyPair(nil, nil) + var cri *tls.CertificateRequestInfo + cri.Context() + cri.SupportsCertificate(nil) + + tls.CipherSuites() + tls.InsecureCipherSuites() + + var chi *tls.ClientHelloInfo + chi.Context() + chi.SupportsCertificate(nil) + + tls.NewLRUClientSessionCache(0) + + conn := tls.Client(nil, nil) + tls.Dial("network", "addr", nil) + tls.DialWithDialer(nil, "network", "addr", nil) + tls.Server(nil, nil) + conn.Close() + conn.CloseWrite() + conn.ConnectionState() + conn.Handshake() + conn.HandshakeContext(nil) + conn.LocalAddr() + conn.NetConn() + conn.OCSPResponse() + conn.Read(nil) + conn.RemoteAddr() + conn.SetDeadline(time.Now()) + conn.SetReadDeadline(time.Now()) + conn.SetWriteDeadline(time.Now()) + conn.VerifyHostname("host") + conn.Write(nil) + + fmt.Println("end of crypto/tls") + +} + +//go:noinline +func cryptoX509Package() { + x509.CreateCertificate(nil, nil, nil, nil, nil) + x509.CreateCertificateRequest(nil, nil, nil) + x509.CreateRevocationList(nil, nil, nil, nil) + x509.MarshalECPrivateKey(nil) + x509.MarshalPKCS1PrivateKey(nil) + x509.MarshalPKCS1PublicKey(nil) + x509.MarshalPKCS8PrivateKey(nil) + x509.MarshalPKIXPublicKey(nil) + x509.ParseECPrivateKey(nil) + x509.ParsePKCS1PrivateKey(nil) + x509.ParsePKCS1PublicKey(nil) + x509.ParsePKCS8PrivateKey(nil) + x509.ParsePKIXPublicKey(nil) + x509.SetFallbackRoots(nil) + certpool := x509.NewCertPool() + x509.SystemCertPool() + certpool.AddCert(nil) + certpool.AppendCertsFromPEM(nil) + certpool.Clone() + certpool.Equal(nil) + cert, _ := x509.ParseCertificate(nil) + x509.ParseCertificates(nil) + cert.CheckSignature(1, nil, nil) + cert.CheckSignatureFrom(nil) + cert.Equal(nil) + cert.Verify(x509.VerifyOptions{}) + cert.VerifyHostname("h") + cr, _ := x509.ParseCertificateRequest(nil) + cr.CheckSignature() + + rl, _ := x509.ParseRevocationList(nil) + rl.CheckSignatureFrom(nil) + + fmt.Println("end of crypto/x509") + +} + +//go:noinline +func databaseSqlPackage() { + sql.Drivers() + sql.Register("name", nil) + // todo +} + +//go:noinline +func debugPackage() { + debugBuildinfoPackage() + // todo: dwarf + debugElfPackage() + +} + +//go:noinline +func debugBuildinfoPackage() { + buildinfo.Read(nil) + buildinfo.ReadFile("name") + + fmt.Println("end of debug/buildinfo") +} + +//go:noinline +func debugElfPackage() { + elf.NewFile(nil) + f, _ := elf.Open("name") + f.Close() + // todo +} + +//go:noinline +func debugGosymPackage() { + gosym.NewLineTable(nil, 0) + gosym.NewTable(nil, nil) +} + +//go:noinline +func errorsPackage() { + err := errors.New("text") + + errors.As(err, err) + errors.Is(err, err) + errors.Join(err, err) + errors.Unwrap(err) + + fmt.Println("end of errors package") +} + +//go:noinline +func flagPackage() { + var b bool + var dur time.Duration + var f64 float64 + var i64 int64 + var i int + var s string + var ui64 uint64 + + flag.Arg(0) + flag.Args() + flag.Bool("name", false, "usage") + //flag.BoolFunc("name", "usage", func (s string) error { return nil } ) // added in 1.21 + flag.BoolVar(&b, "name", false, "usage") + flag.Duration("name", 0, "usage") + flag.DurationVar(&dur, "name", 0, "usage") + flag.Float64("name", 0, "usage") + flag.Float64Var(&f64, "name", 0, "usage") + flag.Func("name", "usage", func(s string) error { return nil }) + flag.Int("name", 0, "usage") + flag.Int64("name", 0, "usage") + flag.Int64Var(&i64, "name", 0, "usage") + flag.IntVar(&i, "name", 0, "usage") + flag.NArg() + flag.NFlag() + flag.Parse() + flag.Parsed() + flag.PrintDefaults() + flag.Set("name", "value") + flag.String("name", "value", "usage") + flag.StringVar(&s, "name", "value", "usage") + + var ip net.IP + flag.TextVar(&ip, "name", net.IPv4(1, 1, 1, 1), "usage") + + flag.Uint("name", 0, "usage") + flag.Uint64("name", 0, "usage") + flag.Uint64Var(&ui64, "name", 0, "usage") + flag.UnquoteUsage(flag.Lookup("name")) + //flag.Var(value, "name", "usage") + flag.Visit(func(f *flag.Flag) {}) + flag.VisitAll(func(f *flag.Flag) {}) + + fs := flag.NewFlagSet("name", 0) + fs.Parse([]string{}) + + fmt.Println("end of flag package") +} + +//go:noinline +func fmtPackage() { + b := make([]byte, 100) + + fmt.Append(b, "string", 55, true) + fmt.Appendf(b, "format %d", 55) + fmt.Appendln(b, "string") + + fmt.Errorf("my error %d", 55) + + fmt.Fprint(os.Stdout, "string", 42) + fmt.Fprintf(os.Stdout, "format %d", 42) + fmt.Fprintln(os.Stdout, "string") + + var i int + var s string + fmt.Fscan(os.Stdin, &i, &s) + fmt.Fscanf(os.Stdin, "%d %s", &i, &s) + fmt.Fscanln(os.Stdin, &s) + + fmt.Print("string") + fmt.Printf("format %d", 43) + fmt.Println("string") + + fmt.Scan(&i, &s) + fmt.Scanf("%d %s", &i, &s) + fmt.Scanln(&i, &s) + + fmt.Sprintf("format %d", 44) + fmt.Sprintln("string") + + fmt.Sscan("string", &s) + fmt.Sscanf("string", "%s", &s) + fmt.Sscanln("string", &s) + + fmt.Println("end of fmt") +} + +func ioAllPackage() { + ioPackage() + ioFsPackage() + ioIoutilPackage() + + fmt.Println("end of io all") +} + +//go:noinline +func ioPackage() { + b := make([]byte, 100) + io.Copy(os.Stdout, os.Stdin) + io.CopyBuffer(os.Stdout, os.Stdin, b) + io.CopyN(os.Stdout, os.Stdin, 0) + io.Pipe() + io.ReadAll(os.Stdin) + io.ReadAtLeast(os.Stdin, b, 0) + io.ReadFull(os.Stdin, b) + io.WriteString(os.Stdout, "s") + file, _ := os.Create("filename") + ow := io.NewOffsetWriter(file, 0) + ow.Seek(0, 0) + ow.Write(b) + ow.WriteAt(b, 0) + pr := io.PipeReader{} + pr.Close() + pr.CloseWithError(nil) + pr.Read(b) + pw := io.PipeWriter{} + pw.Close() + pw.CloseWithError(nil) + pw.Write(b) + rc := io.NopCloser(os.Stdin) + rc.Close() + rc.Read(b) + io.LimitReader(os.Stdin, 0) + io.MultiReader(os.Stdin, os.Stdin) + io.TeeReader(os.Stdin, os.Stdout) + sr := io.NewSectionReader(file, 0, 0) + sr.Read(b) + sr.ReadAt(b, 0) + sr.Size() + io.MultiWriter(os.Stdout) + + fmt.Println("end of io package") +} + +//go:noinline +func ioIoutilPackage() { + b := make([]byte, 100) + + ioutil.NopCloser(os.Stdin) + ioutil.ReadAll(os.Stdin) + ioutil.ReadDir("dirname") + ioutil.ReadFile("filename") + ioutil.TempDir("dir", "pattern") + ioutil.TempFile("dir", "pattern") + ioutil.WriteFile("filename", b, fs.ModeAppend) + + fmt.Println("end of io/ioutil package") +} + +//go:noinline +func ioFsPackage() { + filesys := os.DirFS("dir") + + fs.Glob(filesys, "pattern") + fs.ReadFile(filesys, "name") + fs.ValidPath("name") + fs.WalkDir(filesys, "root", func(path string, d fs.DirEntry, err error) error { return nil }) + //fs.FileInfoToDirEntry( ) + fs.ReadDir(filesys, "name") + fs.Sub(filesys, "dir") + fi, _ := fs.Stat(filesys, "name") + fi.Name() + fi.Size() + fi.Mode() + fi.ModTime() + fi.IsDir() + fi.Sys() + + fmt.Println("end of io/fs package") +} + +func logAllPackage() { + logPackage() + logSyslogPackage() + + fmt.Println("end of log all") +} + +//go:noinline +func logPackage() { + if mathrand.Int() == 0 { + log.Fatal("string") + } + if mathrand.Int() == 0 { + log.Fatalf("format %s", "args") + } + if mathrand.Int() == 0 { + log.Fatalln("string") + } + log.Flags() + log.Output(0, "string") + if mathrand.Int() == 0 { + log.Panic("string") + } + if mathrand.Int() == 0 { + log.Panicf("format %s", "string") + } + if mathrand.Int() == 0 { + log.Panicln("string") + } + log.Prefix() + log.Print("string") + log.Printf("format %s", "string") + log.Println("string") + log.SetFlags(0) + log.SetOutput(os.Stdout) + log.Writer() + + l := log.Default() + log.New(os.Stdout, "prefix", 0) + if mathrand.Int() == 0 { + l.Fatal("string") + } + if mathrand.Int() == 0 { + l.Fatalf("format %s", "string") + } + if mathrand.Int() == 0 { + l.Fatalln("string") + } + if mathrand.Int() == 0 { + l.Panic("string") + } + if mathrand.Int() == 0 { + l.Panicf("format %s", "string") + } + if mathrand.Int() == 0 { + l.Panicln("string") + } + l.Prefix() + l.Print("string") + l.Printf("format %s", "string") + l.Println("string") + l.SetFlags(0) + l.SetOutput(os.Stdout) + l.Writer() + + fmt.Println("end of log package") +} + +// syslog package is not present when GOOS=windows +// +//go:noinline +func logSyslogPackage() { + /* + syslog.NewLogger(0, 0) + syslog.Dial("network", "raddr", 0, "tag") + l, _ := syslog.New(0, "tag") + l.Alert("m") + l.Close() + l.Crit("m") + l.Debug("m") + l.Emerg("m") + l.Err("m") + l.Info("m") + l.Notice("m") + l.Warning("m") + l.Write(make([]byte, 100)) + */ + fmt.Println("end of log/syslog package") +} + +//go:noinline +func mathAllPackage() { + mathPackage() + mathBigPackage() + mathRandPackage() + + fmt.Println("end of math all") +} + +//go:noinline +func mathPackage() { + math.Abs(0) + math.Acos(0) + math.Acosh(0) + math.Asin(0) + math.Atan(0) + math.Atan2(0, 0) + math.Atanh(0) + math.Cbrt(0) + math.Ceil(0) + math.Cos(0) + math.Cosh(0) + math.Dim(0, 0) + math.Erf(0) + math.Erfc(0) + math.Erfcinv(0) + math.Erfinv(0) + math.Exp(0) + math.Exp2(0) + math.Expm1(0) + math.FMA(0, 0, 0) + math.Float32bits(0) + math.Float32frombits(0) + math.Float64bits(0) + math.Float64frombits(0) + math.Floor(0) + math.Frexp(0) + math.Gamma(0) + math.Hypot(0, 0) + math.Ilogb(0) + math.IsInf(0, 1) + math.IsNaN(0) + math.J0(0) + math.J1(0) + math.Ldexp(0, 0) + math.Lgamma(0) + math.Log(0) + math.Log10(0) + math.Log1p(0) + math.Log2(0) + math.Logb(0) + math.Max(0, 0) + math.Min(0, 0) + math.Mod(0, 1) + math.Modf(0) + math.NaN() + math.Nextafter(0, 0) + math.Nextafter32(0, 0) + math.Pow(0, 0) + math.Pow10(0) + math.Remainder(0, 1) + math.Round(0) + math.RoundToEven(0) + math.Signbit(0) + math.Sin(0) + math.Sincos(0) + math.Sinh(0) + math.Sqrt(0) + math.Tan(0) + math.Tanh(0) + math.Trunc(0) + math.Y0(0) + math.Y1(0) + math.Yn(0, 0) + + fmt.Println("end of math") +} + +//go:noinline +func mathBigPackage() { + b := make([]byte, 100) + i := big.NewInt(0) + big.Jacobi(i, i) + f := big.NewFloat(0) + big.ParseFloat("s", 0, 0, big.AwayFromZero) + f.Abs(f) + f.Acc() + f.Add(f, f) + f.Append(b, 0, 0) + f.Cmp(f) + f.Copy(f) + f.Float32() + f.Float64() + f.Format(nil, 0) + f.GobDecode(b) + f.GobEncode() + f.Int(i) + f.Int64() + f.IsInf() + f.IsInf() + f.MantExp(f) + f.MarshalText() + f.MinPrec() + f.Mode() + f.Mul(f, f) + f.Neg(f) + f.Parse("s", 0) + f.Prec() + f.Quo(f, f) + f.Rat(big.NewRat(0, 0)) + f.Scan(nil, 0) + f.Set(f) + f.SetFloat64(0) + f.SetInf(true) + f.SetInt(i) + f.SetInt64(0) + f.SetMantExp(f, 0) + f.SetMode(big.AwayFromZero) + f.SetPrec(0) + f.SetRat(big.NewRat(0, 0)) + f.SetString("s") + f.SetUint64(0) + f.Sign() + f.Signbit() + f.Sqrt(f) + f.String() + f.Sub(f, f) + f.Text(0, 0) + f.Uint64() + f.UnmarshalText(b) + + i.Abs(i) + i.Add(i, i) + i.And(i, i) + i.AndNot(i, i) + i.Append(b, 0) + i.Binomial(0, 0) + i.Bit(0) + i.BitLen() + i.Bits() + i.Bytes() + i.Cmp(i) + i.CmpAbs(i) + i.Div(i, i) + i.DivMod(i, i, i) + i.Exp(i, i, i) + i.FillBytes(b) + i.Format(nil, 0) + i.GCD(i, i, i, i) + i.GobDecode(b) + i.GobEncode() + i.Int64() + i.IsInt64() + i.IsUint64() + i.Lsh(i, 0) + i.MarshalJSON() + i.MarshalText() + i.Mod(i, i) + i.ModInverse(i, i) + i.ModSqrt(i, i) + i.Mul(i, i) + i.MulRange(0, 0) + i.Neg(i) + i.Not(i) + i.Or(i, i) + i.ProbablyPrime(0) + i.Quo(i, i) + i.QuoRem(i, i, i) + i.Rand(&mathrand.Rand{}, i) + i.Rem(i, i) + i.Rsh(i, 0) + i.Scan(nil, 0) + i.Set(i) + i.SetBit(i, 0, 0) + i.SetBits([]big.Word{}) + i.SetBytes(b) + i.SetInt64(0) + i.SetString("s", 0) + i.SetUint64(0) + i.Sign() + i.Sqrt(i) + i.String() + i.Sub(i, i) + i.Text(0) + i.TrailingZeroBits() + i.Uint64() + i.UnmarshalJSON(b) + i.UnmarshalText(b) + i.Xor(i, i) + + r := big.NewRat(0, 0) + r.Abs(r) + r.Cmp(r) + r.Denom() + r.Float32() + r.Float64() + r.FloatString(0) + r.GobDecode(b) + r.GobEncode() + r.Inv(r) + r.IsInt() + r.MarshalText() + r.Mul(r, r) + r.Neg(r) + r.Num() + r.Quo(r, r) + r.RatString() + r.Scan(nil, 0) + r.Set(r) + r.SetFloat64(0) + r.SetFrac(i, i) + r.SetFrac64(0, 0) + r.SetInt(i) + r.SetInt64(0) + r.SetString("s") + r.SetUint64(0) + r.Sign() + r.String() + r.Sub(r, r) + r.UnmarshalText(b) + + fmt.Println("end of math/big") +} + +//go:noinline +func mathRandPackage() { + mathrand.ExpFloat64() + mathrand.Float32() + mathrand.Float64() + mathrand.Int() + mathrand.Int31() + mathrand.Int31n(0) + mathrand.Int63() + mathrand.Int63n(0) + mathrand.Intn(0) + mathrand.NormFloat64() + mathrand.Perm(0) + mathrand.Shuffle(0, func(i, j int) {}) + mathrand.Uint32() + mathrand.Uint64() + rnd := mathrand.New(mathrand.NewSource(0)) + rnd.ExpFloat64() + rnd.Float32() + rnd.Float64() + rnd.Int() + rnd.Int31() + rnd.Int31n(0) + rnd.Int63() + rnd.Int63n(0) + rnd.Intn(0) + rnd.NormFloat64() + rnd.Perm(0) + rnd.Read(make([]byte, 100)) + rnd.Seed(0) + rnd.Shuffle(0, func(i, j int) {}) + rnd.Uint32() + rnd.Uint64() + zipf := mathrand.NewZipf(rnd, 0, 0, 0) + zipf.Uint64() + + fmt.Println("end of math/rand") +} + +//go:noinline +func netAllPackage() { + netPackage() + netHttpPackage() + netUrlPackage() +} + +//go:noinline +func netPackage() { + net.JoinHostPort("host", "port") + net.LookupAddr("addr") + net.LookupCNAME("host") + net.LookupHost("host") + net.LookupPort("network", "service") + net.LookupTXT("name") + net.ParseCIDR("s") + net.Pipe() + net.SplitHostPort("hostport") + net.InterfaceAddrs() + net.Dial("network", "address") + net.DialTimeout("network", "address", 0) + net.FileConn(nil) + net.ResolveIPAddr("network", "address") + net.DialIP("network", nil, nil) + ipconn, _ := net.ListenIP("network", nil) + ipconn.Close() + ipconn.File() + ipconn.LocalAddr() + ipconn.Read(nil) + ipconn.ReadFrom(nil) + ipconn.ReadFromIP(nil) + ipconn.ReadMsgIP(nil, nil) + ipconn.RemoteAddr() + ipconn.SetDeadline(time.Now()) + ipconn.SetReadBuffer(0) + ipconn.SetReadDeadline(time.Now()) + ipconn.SetWriteBuffer(0) + ipconn.SetWriteDeadline(time.Now()) + ipconn.SyscallConn() + ipconn.Write(nil) + ipconn.WriteMsgIP(nil, nil, nil) + ipconn.WriteTo(nil, nil) + ipconn.WriteToIP(nil, nil) + net.CIDRMask(0, 0) + net.IPv4Mask(0, 0, 0, 0) + iface, _ := net.InterfaceByIndex(0) + net.InterfaceByName("name") + net.Interfaces() + iface.Addrs() + iface.MulticastAddrs() + + net.FileListener(nil) + net.Listen("network", "address") + net.LookupMX("name") + net.LookupNS("name") + net.FilePacketConn(nil) + net.ListenPacket("network", "address") + + var res *net.Resolver + res.LookupAddr(nil, "addr") + res.LookupCNAME(nil, "host") + res.LookupHost(nil, "host") + res.LookupIP(nil, "network", "host") + res.LookupIPAddr(nil, "host") + + net.LookupSRV("service", "proto", "name") + net.ResolveTCPAddr("network", "address") + //net.TCPAddrFromAddrPort( netip.AddrPort{}) + + tcpconn, _ := net.DialTCP("network", nil, nil) + tcpconn.Close() + tcpconn.CloseRead() + tcpconn.CloseWrite() + tcpconn.File() + tcpconn.LocalAddr() + tcpconn.Read(nil) + tcpconn.ReadFrom(nil) + tcpconn.RemoteAddr() + tcpconn.SetDeadline(time.Now()) + tcpconn.SetKeepAlive(true) + tcpconn.SetKeepAlivePeriod(0) + tcpconn.SetLinger(0) + tcpconn.SetNoDelay(true) + tcpconn.SetReadBuffer(0) + tcpconn.SetReadDeadline(time.Now()) + tcpconn.SetWriteBuffer(0) + tcpconn.SetWriteDeadline(time.Now()) + tcpconn.SyscallConn() + tcpconn.Write(nil) + + tcplist, _ := net.ListenTCP("network", nil) + tcplist.Accept() + tcplist.Addr() + tcplist.Close() + tcplist.File() + tcplist.SetDeadline(time.Now()) + tcplist.SyscallConn() + + net.ResolveUDPAddr("network", "address") + //net.UDPAddrFromAddrPort() + net.DialUDP("network", nil, nil) + net.ListenMulticastUDP("network", nil, nil) + net.ListenUDP("network", nil) + + net.ResolveUnixAddr("network", "address") + net.DialUnix("network", nil, nil) + net.ListenUnixgram("network", nil) + net.ListenUnix("network", nil) + + fmt.Println("end of net") + +} + +//go:noinline +func netHttpPackage() { + http.CanonicalHeaderKey("s") + http.DetectContentType(nil) + http.Error(nil, "error", 0) + http.Handle("pattern", nil) + http.HandleFunc("pattern", http.NotFound) + http.ListenAndServe("addr", nil) + http.ListenAndServeTLS("addr", "certfile", "keyfile", nil) + http.MaxBytesReader(nil, nil, 0) + http.NotFound(nil, nil) + http.ParseHTTPVersion("vers") + http.ParseTime("text") + http.ProxyFromEnvironment(nil) + http.ProxyURL(nil) + http.Redirect(nil, nil, "url", 0) + http.Serve(nil, nil) + http.ServeContent(nil, nil, "name", time.Now(), nil) + http.ServeFile(nil, nil, "name") + http.ServeTLS(nil, nil, "certfile", "keyfile") + http.SetCookie(nil, nil) + http.StatusText(0) + var client *http.Client + client.CloseIdleConnections() + client.Do(nil) + client.Get("url") + client.Head("url") + client.Post("url", "contenttype", nil) + client.PostForm("url", nil) + var cookie *http.Cookie + cookie.String() + cookie.Valid() + http.AllowQuerySemicolons(nil) + http.FileServer(nil) + http.MaxBytesHandler(nil, 0) + http.NotFoundHandler() + http.RedirectHandler("url", 0) + http.StripPrefix("prefix", nil) + http.TimeoutHandler(nil, 0, "msg") + var header http.Header + header.Add("key", "value") + header.Clone() + header.Del("key") + header.Get("key") + header.Set("key", "value") + header.Values("key") + header.Write(nil) + header.WriteSubset(nil, nil) + + req, _ := http.NewRequest("method", "url", nil) + http.NewRequestWithContext(nil, "method", "url", nil) + http.ReadRequest(nil) + req.AddCookie(nil) + req.BasicAuth() + req.Clone(nil) + req.Context() + req.Cookie("name") + req.Cookies() + req.FormFile("key") + req.MultipartReader() + req.ParseForm() + req.ParseMultipartForm(0) + req.PostFormValue("key") + req.ProtoAtLeast(0, 0) + req.Referer() + req.SetBasicAuth("username", "password") + req.UserAgent() + req.WithContext(context.Background()) + req.Write(nil) + req.WriteProxy(nil) + res, _ := http.Get("url") + http.Head("url") + http.Post("url", "contenttype", nil) + http.PostForm("url", nil) + http.ReadResponse(nil, nil) + res.Cookies() + res.Location() + res.ProtoAtLeast(0, 0) + res.Write(nil) + http.NewResponseController(nil) + http.NewFileTransport(nil) + http.NewServeMux() + var srv *http.Server + srv.Close() + srv.ListenAndServe() + srv.ListenAndServeTLS("certfile", "keyfile") + srv.RegisterOnShutdown(nil) + srv.Serve(nil) + srv.ServeTLS(nil, "certfile", "keyfile") + srv.SetKeepAlivesEnabled(true) + srv.Shutdown(nil) + + var t *http.Transport + t.Clone() + t.CloseIdleConnections() + t.RegisterProtocol("scheme", nil) + t.RoundTrip(nil) + + fmt.Println("end of net/http") + +} + +//go:noinline +func netUrlPackage() { + url.JoinPath("base", "elem") + url.PathEscape("s") + url.PathUnescape("s") + url.QueryEscape("s") + url.QueryUnescape("s") + + u, _ := url.Parse("s") + url.ParseRequestURI("s") + u.EscapedFragment() + u.EscapedPath() + u.Hostname() + u.IsAbs() + u.JoinPath("s") + u.MarshalBinary() + u.Parse("ref") + u.Port() + u.Query() + u.Redacted() + u.RequestURI() + u.ResolveReference(u) + u.String() + u.UnmarshalBinary(nil) + user := url.User("username") + url.UserPassword("username", "password") + user.Password() + user.String() + user.Username() + vals, _ := url.ParseQuery("query") + vals.Add("key", "value") + vals.Del("key") + vals.Encode() + vals.Get("key") + vals.Has("key") + vals.Set("key", "value") + + fmt.Println("end of net/url") +} + +//go:noinline +func osPackage() { + var err error + + os.Chdir("path") + os.Chmod("filename", 0) + os.Chown("filename", 0, 0) + os.Chtimes("filename", time.Now(), time.Now()) + os.Clearenv() + os.DirFS("dir") + os.Environ() + os.Executable() + os.Exit(1) + os.Expand("string", nil) + os.ExpandEnv("string") + os.Getegid() + os.Getenv("string") + os.Geteuid() + os.Getgid() + os.Getgroups() + os.Getpagesize() + os.Getpid() + os.Getppid() + os.Getuid() + os.Getwd() + os.Hostname() + os.IsExist(err) + os.IsNotExist(err) + os.IsPathSeparator(0) + os.IsPermission(err) + os.IsTimeout(err) + os.Lchown("filename", 0, 0) + os.Link("filename", "filename") + os.LookupEnv("key") + os.Mkdir("filename", 0) + os.MkdirAll("filename", 0) + os.MkdirTemp("filename", "pattern") + os.NewSyscallError("string", err) + os.Pipe() + os.ReadFile("filename") + os.Readlink("filename") + os.Remove("filename") + os.RemoveAll("path") + os.Rename("filename", "filename") + os.SameFile(nil, nil) + os.Setenv("key", "value") + os.Symlink("filename", "filename") + os.TempDir() + os.Truncate("filename", 0) + os.Unsetenv("key") + os.UserCacheDir() + os.UserConfigDir() + os.UserHomeDir() + os.WriteFile("filename", nil, 0) + + os.ReadDir("dir") + + file, _ := os.Create("filename") + os.CreateTemp("dir", "pattern") + os.NewFile(0, "filename") + os.Open("filename") + os.OpenFile("filename", 1, 0) + + b := make([]byte, 100) + + file.Chdir() + file.Chmod(0) + file.Chown(0, 0) + file.Close() + file.Fd() + file.Name() + file.Read(b) + file.ReadAt(b, 0) + file.ReadDir(0) + file.ReadFrom(nil) + file.Readdir(0) + file.Readdirnames(0) + file.Seek(0, 0) + file.SetDeadline(time.Now()) + file.SetReadDeadline(time.Now()) + file.SetWriteDeadline(time.Now()) + file.Stat() + file.Sync() + file.SyscallConn() + file.Truncate(0) + file.Write(b) + file.WriteAt(b, 0) + file.WriteString("string") + + os.Lstat("filename") + os.Stat("filename") + + os.FindProcess(0) + p, _ := os.StartProcess("name", []string{}, nil) + + p.Kill() + p.Release() + p.Signal(nil) + ps, _ := p.Wait() + + ps.ExitCode() + ps.Exited() + ps.Pid() + ps.String() + ps.Success() + ps.Sys() + ps.SysUsage() + ps.SystemTime() + ps.UserTime() + + fmt.Println("end of os") +} + +//go:noinline +func pathPackage() { + path.Base("path") + path.Clean("path") + path.Dir("path") + path.Ext("path") + path.IsAbs("path") + path.Join("path1", "path2") + path.Match("pattern", "name") + path.Split("path") + + fmt.Println("end of path") +} + +//go:noinline +func pathFilepathPackage() { + filepath.Abs("path") + filepath.Base("path") + filepath.Clean("path") + filepath.Dir("path") + filepath.EvalSymlinks("path") + filepath.Ext("path") + filepath.FromSlash("path") + filepath.Glob("pattern") + filepath.IsAbs("path") + filepath.IsLocal("path") + filepath.Join("path1", "path2") + filepath.Match("pattern", "name") + filepath.Rel("base", "targ") + filepath.Split("path") + filepath.SplitList("path") + filepath.ToSlash("path") + filepath.VolumeName("path") + filepath.Walk("root", func(path string, info fs.FileInfo, err error) error { return nil }) + filepath.WalkDir("root", func(path string, d fs.DirEntry, err error) error { return nil }) + + fmt.Println("end of path/filepath") +} + +//go:noinline +func pluginPackage() { + p, _ := plugin.Open("path") + sym, _ := p.Lookup("sym") + fmt.Printf("sym: %v\n", sym) + + fmt.Println("end of plugin") +} + +//go:noinline +func runtimePackage() { + runtime.BlockProfile(nil) + runtime.Breakpoint() + //runtime.CPUProfile() + runtime.Caller(0) + runtime.Callers(0, nil) + runtime.GC() + runtime.GOMAXPROCS(0) + runtime.GOROOT() + //runtime.Goexit() + runtime.GoroutineProfile(nil) + runtime.Gosched() + runtime.KeepAlive(nil) + runtime.LockOSThread() + runtime.MemProfile(nil, true) + runtime.MutexProfile(nil) + runtime.NumCPU() + runtime.NumCgoCall() + runtime.NumGoroutine() + runtime.ReadMemStats(nil) + runtime.ReadTrace() + runtime.SetBlockProfileRate(0) + runtime.SetCPUProfileRate(0) + runtime.SetCgoTraceback(0, nil, nil, nil) + runtime.SetFinalizer(nil, nil) + runtime.SetMutexProfileFraction(0) + runtime.Stack(nil, true) + runtime.StartTrace() + runtime.StopTrace() + runtime.ThreadCreateProfile(nil) + runtime.UnlockOSThread() + runtime.Version() + + f := runtime.FuncForPC(0) + f.Entry() + f.FileLine(0) + f.Name() + + fmt.Println("end of runtime") + +} + +//go:noinline +func timePackage() { + b := make([]byte, 100) + + fmt.Println(time.After(0)) + time.Sleep(0) + fmt.Println(time.Tick(0)) + fmt.Println(time.ParseDuration("s")) + fmt.Println(time.Since(time.Now())) + d := time.Until(time.Now()) + d += d.Abs() + fmt.Println(d.Hours()) + ms := d.Microseconds() + ms += d.Milliseconds() + ms += int64(d.Minutes()) + ms += d.Nanoseconds() + ms += int64(d.Round(0)) + ms += int64(d.Seconds()) + fmt.Println(d.String()) + ms += int64(d.Truncate(0)) + fmt.Println(ms) + + loc := time.FixedZone("name", 0) + time.LoadLocation("name") + time.LoadLocationFromTZData("name", nil) + fmt.Println(loc.String()) + t := time.NewTicker(0) + t.Reset(0) + t.Stop() + fmt.Println(time.Date(0, 0, 0, 0, 0, 0, 0, nil)) + fmt.Println(time.Now()) + fmt.Println(time.Parse("layout", "value")) + time.ParseInLocation("layout", "value", nil) + time.Unix(0, 0) + time.UnixMicro(0) + tm := time.UnixMilli(0) + tm.Add(0) + tm.AddDate(0, 0, 0) + tm.After(tm) + tm.AppendFormat(nil, "layout") + tm.Before(tm) + tm.Clock() + tm.Compare(tm) + tm.Date() + tm.Day() + fmt.Println(tm.Equal(tm)) + fmt.Println(tm.Format("layout")) + fmt.Println(tm.GoString()) + tm.GobDecode(b) + tm.Hour() + tm.ISOWeek() + tm.In(time.Local) + tm.IsDST() + tm.IsZero() + tm.Local() + tm.Location() + tm.MarshalBinary() + tm.MarshalJSON() + tm.MarshalText() + tm.Minute() + tm.Month() + tm.Nanosecond() + tm.Round(0) + tm.Second() + tm.String() + tm.Sub(tm) + tm.Truncate(0) + tm.UTC() + tm.Unix() + tm.UnixMicro() + tm.UnixMilli() + tm.UnixNano() + tm.UnmarshalBinary(b) + tm.UnmarshalJSON(b) + tm.UnmarshalText(b) + tm.Weekday() + tm.Year() + tm.YearDay() + tm.Zone() + tm.ZoneBounds() + time.AfterFunc(0, nil) + tmr := time.NewTimer(0) + tmr.Reset(0) + tmr.Stop() + + fmt.Println("end of time") +} + +//go:noinline +func textPackage() { + textScannerPackage() + textTabwriterPackage() + textTemplatePackage() +} + +//go:noinline +func textScannerPackage() { + var s scanner.Scanner + scanner.TokenString(0) + s.Init(nil) + s.Next() + s.Peek() + s.Pos() + s.Scan() + s.TokenText() + + fmt.Println("end of text/scanner") +} + +//go:noinline +func textTabwriterPackage() { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, 0, 0) + w.Flush() + w.Init(os.Stdout, 0, 0, 0, 0, 0) + w.Write(nil) + + fmt.Println("end of text/tabwriter") +} + +//go:noinline +func textTemplatePackage() { + template.HTMLEscape(os.Stdout, nil) + template.HTMLEscapeString("s") + template.HTMLEscaper("s") + template.IsTrue("s") + template.JSEscape(os.Stdout, nil) + template.JSEscapeString("s") + template.JSEscaper("s") + template.URLQueryEscaper("s") + t := template.Must(template.New("name"), nil) + t.AddParseTree("name", nil) + t.Clone() + t.DefinedTemplates() + t.Delims("left", "right") + t.Execute(os.Stdout, "blah") + t.ExecuteTemplate(os.Stdout, "name", "any") + t.Funcs(template.FuncMap{}) + t.Lookup("name") + t.Name() + t.New("name") + t.Option("opt1") + t.Parse("test") + t.ParseFS(nil, "pattern") + t.ParseFiles("filename") + t.ParseGlob("pattern") + t.Templates() + + fmt.Println("end of text/template") + +} + +//go:noinline +func strconvPackage() { + b := make([]byte, 1000) + strconv.AppendBool(b, true) + strconv.AppendFloat(b, 1.1, 0, 0, 0) + strconv.AppendInt(b, 0, 0) + strconv.AppendQuote(b, "s") + strconv.AppendQuoteRune(b, 0) + strconv.AppendQuoteRuneToASCII(b, 0) + strconv.AppendQuoteRuneToGraphic(b, 0) + strconv.AppendQuoteToASCII(b, "s") + strconv.AppendQuoteToGraphic(b, "s") + strconv.AppendUint(b, 0, 0) + strconv.Atoi("s") + strconv.CanBackquote("s") + strconv.FormatBool(true) + strconv.FormatComplex(complex(1, 1), 0, 0, 0) + strconv.FormatFloat(1, 0, 0, 0) + strconv.FormatInt(0, 0) + strconv.FormatUint(0, 0) + strconv.IsGraphic(0) + strconv.IsPrint(0) + strconv.Itoa(0) + strconv.ParseBool("b") + strconv.ParseComplex("s", 0) + strconv.ParseInt("s", 0, 0) + strconv.ParseUint("s", 0, 0) + strconv.Quote("s") + strconv.QuoteRune(0) + strconv.QuoteRuneToASCII(0) + strconv.QuoteRuneToGraphic(0) + strconv.QuoteToASCII("s") + strconv.QuoteToGraphic("s") + strconv.Unquote("s") + + fmt.Println("end of strconv") +} + +//go:noinline +func syncPackage() { + cond := sync.NewCond(&sync.Mutex{}) + cond.Broadcast() + cond.Signal() + cond.Wait() + m := sync.Map{} + m.CompareAndDelete("key", "old") + m.CompareAndSwap("key", "old", "new") + m.Delete("key") + m.Load("value") + m.LoadAndDelete("key") + m.LoadOrStore("key", "value") + m.Range(nil) + m.Store("key", "value") + m.Swap("key", "value") + + mux := sync.Mutex{} + mux.Lock() + mux.TryLock() + mux.Unlock() + once := sync.Once{} + once.Do(nil) + + pool := sync.Pool{} + pool.Get() + pool.Put("any") + + rwmux := sync.RWMutex{} + rwmux.Lock() + rwmux.RLock() + rwmux.RLocker() + rwmux.RUnlock() + rwmux.TryLock() + rwmux.TryRLock() + rwmux.Unlock() + wg := sync.WaitGroup{} + wg.Add(0) + wg.Done() + wg.Wait() + + fmt.Println("end of sync") +} + +//go:noinline +func stringsPackage() { + strings.Clone("s") + strings.Compare("a", "b") + strings.Contains("s", "s") + strings.ContainsAny("s", "chars") + strings.ContainsRune("s", 0) + strings.Count("s", "substr") + strings.Cut("s", "sep") + strings.CutPrefix("s", "prefix") + strings.CutSuffix("s", "suffix") + strings.EqualFold("s", "t") + strings.Fields("s") + strings.FieldsFunc("s", func(r rune) bool { return true }) + strings.HasPrefix("s", "prefix") + strings.HasSuffix("s", "suffix") + strings.Index("s", "substr") + strings.IndexAny("s", "chars") + strings.IndexFunc("s", func(r rune) bool { return true }) + strings.IndexRune("s", 0) + strings.Join([]string{"s", "s"}, "sep") + strings.LastIndex("s", "substr") + strings.LastIndexAny("s", "chars") + strings.LastIndexByte("s", 0) + strings.LastIndexFunc("s", func(r rune) bool { return true }) + strings.Map(func(r rune) rune { return r }, "s") + strings.Repeat("s", 0) + strings.Replace("s", "old", "new", 0) + strings.ReplaceAll("s", "old", "new") + strings.Split("s", "sep") + strings.SplitAfter("s", "sep") + strings.SplitAfterN("s", "sep", 0) + strings.SplitN("s", "sep", 0) + strings.ToLower("s") + strings.ToLowerSpecial(unicode.TurkishCase, "s") + strings.ToTitle("s") + strings.ToTitleSpecial(unicode.TurkishCase, "s") + strings.ToUpper("s") + strings.ToUpperSpecial(unicode.AzeriCase, "s") + strings.ToValidUTF8("s", "replace") + strings.Trim("s", "cutset") + strings.TrimFunc("s", func(r rune) bool { return true }) + strings.TrimLeft("s", "cutset") + strings.TrimLeftFunc("s", func(r rune) bool { return true }) + strings.TrimRight("s", "cutset") + strings.TrimRightFunc("s", func(r rune) bool { return true }) + strings.TrimSpace("s") + strings.TrimSuffix("s", "suffix") + b := strings.Builder{} + b.Cap() + b.Grow(0) + b.Len() + b.Reset() + b.String() + b.Write(nil) + b.WriteByte(0) + b.WriteRune(0) + b.WriteString("s") + r := strings.NewReader("s") + r.Len() + r.Read(nil) + r.ReadAt(nil, 0) + r.ReadByte() + r.ReadRune() + r.Reset("s") + r.Seek(0, 0) + r.Size() + r.UnreadByte() + r.UnreadRune() + r.WriteTo(os.Stdout) + + rp := strings.NewReplacer("a", "b") + rp.Replace("s") + + fmt.Println("end of strings") +} + +//go:noinline +func sortPackage() { + sort.Find(0, func(i int) int { return i }) + sort.Float64s([]float64{}) + sort.Float64sAreSorted([]float64{}) + sort.Ints([]int{}) + sort.IntsAreSorted([]int{}) + sort.IsSorted(sort.StringSlice{"1", "2"}) + sort.Search(1, func(i int) bool { return true }) + sort.SearchFloat64s([]float64{}, 0) + sort.SearchInts([]int{}, 0) + sort.SearchStrings([]string{}, "x") + sort.Slice([]string{"x"}, func(i, j int) bool { return true }) + sort.SliceIsSorted([]string{}, func(i, j int) bool { return true }) + sort.SliceStable([]string{}, func(i, j int) bool { return true }) + sort.Sort(sort.StringSlice{}) + sort.Stable(sort.StringSlice{}) + sort.Strings([]string{}) + sort.StringsAreSorted([]string{}) + f64s := sort.Float64Slice{1, 2} + f64s.Len() + f64s.Less(0, 0) + f64s.Search(0) + f64s.Sort() + f64s.Swap(0, 1) + sort.Reverse(f64s) + + fmt.Println("end of sort") + +} + +//go:noinline +func unicodeAllPackages() { + unicodePackage() + unicodeUtf16Package() + unicodeUtf8Package() + + fmt.Println("end of all unicode packages") +} + +//go:noinline +func unicodePackage() { + var r rune = mathrand.Int31() + unicode.In(r, unicode.Latin) + unicode.Is(unicode.Latin, r) + unicode.IsControl(r) + unicode.IsDigit(r) + unicode.IsGraphic(r) + unicode.IsLetter(r) + unicode.IsLower(r) + unicode.IsMark(r) + unicode.IsNumber(r) + unicode.IsOneOf([]*unicode.RangeTable{unicode.Latin}, r) + unicode.IsPrint(r) + unicode.IsPunct(r) + unicode.IsSpace(r) + unicode.IsSymbol(r) + unicode.IsTitle(r) + unicode.IsUpper(r) + unicode.SimpleFold(r) + unicode.To(unicode.UpperCase, r) + unicode.ToLower(r) + unicode.ToTitle(r) + unicode.ToUpper(r) + + fmt.Println("end of unicode") +} + +//go:noinline +func unicodeUtf16Package() { + var r rune = mathrand.Int31() + var p []uint16 + p = utf16.AppendRune(p, r) + var s = utf16.Decode(p) + r = utf16.DecodeRune(r, r) + p = utf16.Encode(s) + r, _ = utf16.EncodeRune(r) + utf16.IsSurrogate(r) + + fmt.Println("end of unicode/utf16") +} + +//go:noinline +func unicodeUtf8Package() { + var r rune = mathrand.Int31() + var p []byte + p = utf8.AppendRune(p, r) + r, _ = utf8.DecodeLastRune(p) + r, _ = utf8.DecodeLastRuneInString("string") + r, _ = utf8.DecodeRune(p) + r, _ = utf8.DecodeRuneInString("string") + utf8.EncodeRune(p, r) + utf8.FullRune(p) + utf8.FullRuneInString("string") + utf8.RuneCount(p) + utf8.RuneCountInString("string") + utf8.RuneLen(r) + utf8.RuneStart(0) + utf8.Valid(p) + utf8.ValidRune(r) + utf8.ValidString("string") + + fmt.Println("end of unicode/utf8") +} + +/* +go:noinline +func unsafePackage() { + var b byte = 0 + var aligned = unsafe.Alignof(b) + fmt.Println(aligned) + fmt.Println(unsafe.Sizeof(b)) + fmt.Println(unsafe.String(&b, 10)) + fmt.Println(unsafe.StringData("string")) + s := unsafe.Slice(&b, 100) + fmt.Println(unsafe.SliceData(s)) + fmt.Println(unsafe.Add(unsafe.Pointer(&b), 100)) + + fmt.Println("end of unsafe package") +} +*/ + +func main() { + archivePackage() + bufioPackage() + bytesPackage() + compressPackage() + containerPackage() + contextPackage() + cryptoPackage() + databaseSqlPackage() + debugPackage() + errorsPackage() + flagPackage() + fmtPackage() + ioAllPackage() + logAllPackage() + mathAllPackage() + netAllPackage() + osPackage() + pathPackage() + pathFilepathPackage() + pluginPackage() + runtimePackage() + syncPackage() + stringsPackage() + strconvPackage() + sortPackage() + textPackage() + timePackage() + unicodeAllPackages() + //unsafePackage() +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisStateInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisStateInfo.java deleted file mode 100644 index 03676c512d..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisStateInfo.java +++ /dev/null @@ -1,81 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 - * - * http://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. - */ -package ghidra.app.plugin.core.analysis; - -import java.util.HashMap; - -import ghidra.program.model.listing.Program; - -/** - * AnalysisStateInfo holds onto AnalysisState information associated with analysis of a - * particular program. An AnalysisState can be associated with an individual analyzer - * or a group of analyzers which maintain information common to those analyzers for a - * program's analysis. The point of an analysis state is to maintain information between - * individual invocations of a particular analyzer or between differing analyzers that - * have information in common. For example, an instruction type of analyzer is invoked - * repeatedly during analysis as various blocks of instructions are created by disassembly. - * If you need to maintain a set of addresses that have been processed by the analyzer so - * that they aren't unnecessarily reprocessed, you could maintain the address set in an - * analysis state. This allows the analysis information to be maintained from one invocation - * to the next or from one analyzer to another. - */ -public class AnalysisStateInfo { - - private static HashMap, AnalysisState>> programStates = - new HashMap<>(); - - private AnalysisStateInfo() { - // no construct - } - - /** - * Return previously stored AnalysisState of the specified analysisStateClass type - * for the specified program. - * @param program - * @param analysisStateClass type of AnalysisState - * @return analysis state or null if not previously stored via {@link #putAnalysisState(Program, AnalysisState)} - */ - @SuppressWarnings("unchecked") // putAnalysisState ensures that stored instance corresponds to key class - public static T getAnalysisState(Program program, - Class analysisStateClass) { - HashMap, AnalysisState> stateMap = - programStates.get(program); - if (stateMap != null) { - // Found the map for this program. - return (T) stateMap.get(analysisStateClass); - } - return null; - } - - /** - * Store/replace a specific AnalysisState implementation for a specific program. - * Note that only a single instance of a given AnaysisState class implementation - * will be stored for a given program. - * @param program - * @param state analysis state instance - */ - public static void putAnalysisState(final Program program, AnalysisState state) { - HashMap, AnalysisState> stateMap = - programStates.get(program); - if (stateMap == null) { - stateMap = new HashMap<>(); - programStates.put(program, stateMap); - program.addCloseListener(doa -> programStates.remove(program)); - } - stateMap.put(state.getClass(), state); - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java index f0a215c001..941a76de47 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java @@ -23,6 +23,7 @@ import ghidra.app.util.bin.format.dwarf4.DWARFPreconditionException; import ghidra.app.util.bin.format.dwarf4.next.*; import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.DWARFSectionProvider; import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.DWARFSectionProviderFactory; +import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; import ghidra.app.util.importer.MessageLog; import ghidra.framework.options.Options; import ghidra.program.model.address.AddressSetView; @@ -34,83 +35,10 @@ import ghidra.util.task.TaskMonitor; public class DWARFAnalyzer extends AbstractAnalyzer { private static final String DWARF_LOADED_OPTION_NAME = "DWARF Loaded"; - - private static final String OPTION_IMPORT_DATATYPES = "Import Data Types"; - private static final String OPTION_IMPORT_DATATYPES_DESC = - "Import data types defined in the DWARF debug info."; - - private static final String OPTION_PRELOAD_ALL_DIES = "Preload All DIEs"; - private static final String OPTION_PRELOAD_ALL_DIES_DESC = - "Preload all DIE records. Requires more memory, but necessary for some non-standard " + - "layouts."; - - private static final String OPTION_IMPORT_FUNCS = "Import Functions"; - private static final String OPTION_IMPORT_FUNCS_DESC = - "Import function information defined in the DWARF debug info\n" + - "(implies 'Import Data Types' is selected)."; - - private static final String OPTION_IMPORT_LIMIT_DIE_COUNT = "Debug Item Limit"; - private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_DESC = - "If the number of DWARF debug items are greater than this setting, DWARF analysis will " + - "be skipped."; - - private static final String OPTION_OUTPUT_SOURCE_INFO = "Output Source Info"; - private static final String OPTION_OUTPUT_SOURCE_INFO_DESC = - "Include source code location info (filename:linenumber) in comments attached to the " + - "Ghidra datatype or function or variable created."; - - private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE Info"; - private static final String OPTION_OUTPUT_DWARF_DIE_INFO_DESC = - "Include DWARF DIE offset info in comments attached to the Ghidra datatype or function " + - "or variable created."; - - private static final String OPTION_NAME_LENGTH_CUTOFF = "Maximum Name Length"; - private static final String OPTION_NAME_LENGTH_CUTOFF_DESC = - "Truncate symbol and type names longer than this limit. Range 20..2000"; - - private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS = "Add Lexical Block Comments"; - private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC = - "Add comments to the start of lexical blocks"; - - private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS = - "Add Inlined Functions Comments"; - private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC = - "Add comments to the start of inlined functions"; - - private static final String OPTION_OUTPUT_FUNC_SIGS = "Create Function Signatures"; - private static final String OPTION_OUTPUT_FUNC_SIGS_DESC = - "Create function signature data types for each function encountered in the DWARF debug " + - "data."; - - private static final String OPTION_TRY_PACK_STRUCTS = "Try To Pack Structs"; - private static final String OPTION_TRY_PACK_STRUCTS_DESC = - "Try to pack structure/union data types."; - private static final String DWARF_ANALYZER_NAME = "DWARF"; private static final String DWARF_ANALYZER_DESCRIPTION = - "Automatically extracts DWARF info from an ELF file."; + "Automatically extracts DWARF info from ELF/MachO/PE files."; -//================================================================================================== -// Old Option Names - Should stick around for multiple major versions after 10.2 -//================================================================================================== - - private static final String OPTION_IMPORT_DATATYPES_OLD = "Import data types"; - private static final String OPTION_PRELOAD_ALL_DIES_OLD = "Preload all DIEs"; - private static final String OPTION_IMPORT_FUNCS_OLD = "Import functions"; - private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_OLD = "Debug item count limit"; - private static final String OPTION_OUTPUT_SOURCE_INFO_OLD = "Output Source info"; - private static final String OPTION_OUTPUT_DWARF_DIE_INFO_OLD = "Output DWARF DIE info"; - private static final String OPTION_NAME_LENGTH_CUTOFF_OLD = "Name length cutoff"; - private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD = "Lexical block comments"; - private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD = - "Inlined functions comments"; - private static final String OPTION_OUTPUT_FUNC_SIGS_OLD = "Output function signatures"; - - private AnalysisOptionsUpdater optionsUpdater = new AnalysisOptionsUpdater(); - -//================================================================================================== -// End Old Option Names -//================================================================================================== /** * Returns true if DWARF has already been imported into the specified program. @@ -132,26 +60,6 @@ public class DWARFAnalyzer extends AbstractAnalyzer { setDefaultEnablement(true); setPriority(AnalysisPriority.FORMAT_ANALYSIS.after()); setSupportsOneTimeAnalysis(); - - optionsUpdater.registerReplacement(OPTION_IMPORT_DATATYPES, - OPTION_IMPORT_DATATYPES_OLD); - optionsUpdater.registerReplacement(OPTION_PRELOAD_ALL_DIES, - OPTION_PRELOAD_ALL_DIES_OLD); - optionsUpdater.registerReplacement(OPTION_IMPORT_FUNCS, - OPTION_IMPORT_FUNCS_OLD); - optionsUpdater.registerReplacement(OPTION_IMPORT_LIMIT_DIE_COUNT, - OPTION_IMPORT_LIMIT_DIE_COUNT_OLD); - optionsUpdater.registerReplacement(OPTION_OUTPUT_SOURCE_INFO, - OPTION_OUTPUT_SOURCE_INFO_OLD); - optionsUpdater.registerReplacement(OPTION_OUTPUT_DWARF_DIE_INFO, - OPTION_OUTPUT_DWARF_DIE_INFO_OLD); - optionsUpdater.registerReplacement(OPTION_NAME_LENGTH_CUTOFF, - OPTION_NAME_LENGTH_CUTOFF_OLD); - optionsUpdater.registerReplacement(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, - OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD); - optionsUpdater.registerReplacement(OPTION_OUTPUT_INLINE_FUNC_COMMENTS, - OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD); - optionsUpdater.registerReplacement(OPTION_OUTPUT_FUNC_SIGS, OPTION_OUTPUT_FUNC_SIGS_OLD); } @Override @@ -186,6 +94,11 @@ public class DWARFAnalyzer extends AbstractAnalyzer { return false; } + if (GoRttiMapper.isGolangProgram(program)) { + Msg.info(this, "DWARF: Enabling DIE preload for golang binary"); + importOptions.setPreloadAllDIEs(true); + } + try { try (DWARFProgram prog = new DWARFProgram(program, importOptions, monitor, dsp)) { if (prog.getRegisterMappings() == null && importOptions.isImportFuncs()) { @@ -235,72 +148,17 @@ public class DWARFAnalyzer extends AbstractAnalyzer { @Override public void registerOptions(Options options, Program program) { - - options.registerOption(OPTION_IMPORT_DATATYPES, importOptions.isImportDataTypes(), null, - OPTION_IMPORT_DATATYPES_DESC); - - options.registerOption(OPTION_PRELOAD_ALL_DIES, importOptions.isPreloadAllDIEs(), null, - OPTION_PRELOAD_ALL_DIES_DESC); - - options.registerOption(OPTION_IMPORT_FUNCS, importOptions.isImportFuncs(), null, - OPTION_IMPORT_FUNCS_DESC); - - options.registerOption(OPTION_OUTPUT_DWARF_DIE_INFO, importOptions.isOutputDIEInfo(), null, - OPTION_OUTPUT_DWARF_DIE_INFO_DESC); - - options.registerOption(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, - importOptions.isOutputLexicalBlockComments(), null, - OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC); - - options.registerOption(OPTION_OUTPUT_INLINE_FUNC_COMMENTS, - importOptions.isOutputInlineFuncComments(), null, - OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC); - - options.registerOption(OPTION_OUTPUT_SOURCE_INFO, - importOptions.isOutputSourceLocationInfo(), null, OPTION_OUTPUT_SOURCE_INFO_DESC); - - options.registerOption(OPTION_IMPORT_LIMIT_DIE_COUNT, - importOptions.getImportLimitDIECount(), null, OPTION_IMPORT_LIMIT_DIE_COUNT_DESC); - - options.registerOption(OPTION_NAME_LENGTH_CUTOFF, importOptions.getNameLengthCutoff(), null, - OPTION_NAME_LENGTH_CUTOFF_DESC); - - options.registerOption(OPTION_OUTPUT_FUNC_SIGS, importOptions.isCreateFuncSignatures(), - null, OPTION_OUTPUT_FUNC_SIGS_DESC); - - options.registerOption(OPTION_TRY_PACK_STRUCTS, importOptions.isTryPackStructs(), - null, OPTION_TRY_PACK_STRUCTS_DESC); + importOptions.registerOptions(options); } @Override public AnalysisOptionsUpdater getOptionsUpdater() { - return optionsUpdater; + return importOptions.getOptionsUpdater(); } @Override public void optionsChanged(Options options, Program program) { - importOptions.setOutputDIEInfo( - options.getBoolean(OPTION_OUTPUT_DWARF_DIE_INFO, importOptions.isOutputDIEInfo())); - importOptions.setPreloadAllDIEs( - options.getBoolean(OPTION_PRELOAD_ALL_DIES, importOptions.isPreloadAllDIEs())); - importOptions.setOutputSourceLocationInfo(options.getBoolean(OPTION_OUTPUT_SOURCE_INFO, - importOptions.isOutputSourceLocationInfo())); - importOptions.setOutputLexicalBlockComments(options.getBoolean( - OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, importOptions.isOutputLexicalBlockComments())); - importOptions.setOutputInlineFuncComments(options.getBoolean( - OPTION_OUTPUT_INLINE_FUNC_COMMENTS, importOptions.isOutputInlineFuncComments())); - importOptions.setImportDataTypes( - options.getBoolean(OPTION_IMPORT_DATATYPES, importOptions.isImportDataTypes())); - importOptions.setImportFuncs( - options.getBoolean(OPTION_IMPORT_FUNCS, importOptions.isImportFuncs())); - importOptions.setImportLimitDIECount( - options.getInt(OPTION_IMPORT_LIMIT_DIE_COUNT, importOptions.getImportLimitDIECount())); - importOptions.setNameLengthCutoff( - options.getInt(OPTION_NAME_LENGTH_CUTOFF, importOptions.getNameLengthCutoff())); - importOptions.setCreateFuncSignatures( - options.getBoolean(OPTION_OUTPUT_FUNC_SIGS, importOptions.isCreateFuncSignatures())); - importOptions.setTryPackDataTypes( - options.getBoolean(OPTION_TRY_PACK_STRUCTS, importOptions.isTryPackStructs())); + importOptions.optionsChanged(options); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangStringAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangStringAnalyzer.java new file mode 100644 index 0000000000..c36732635c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangStringAnalyzer.java @@ -0,0 +1,376 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.plugin.core.analysis; + +import java.io.IOException; +import java.util.function.Predicate; + +import ghidra.app.services.*; +import ghidra.app.util.bin.format.golang.GoParamStorageAllocator; +import ghidra.app.util.bin.format.golang.rtti.*; +import ghidra.app.util.bin.format.golang.structmapping.MarkupSession; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.Options; +import ghidra.program.model.address.*; +import ghidra.program.model.data.Pointer; +import ghidra.program.model.data.Undefined; +import ghidra.program.model.lang.OperandType; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.scalar.Scalar; +import ghidra.program.model.symbol.*; +import ghidra.util.Msg; +import ghidra.util.StringUtilities; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import ghidra.util.task.UnknownProgressWrappingTaskMonitor; + +/** + * Analyzer that finds Golang strings (and optionally slices) and marks up the found instances. + *

+ * The char[] data for Golang strings does not contain null terminators, so the normal logic already + * built into Ghidra to find terminated strings doesn't work. + *

+ * This implementation looks for data that matches what a Golang string + * struct { char* data, long len } would look like, and follows the pointer to the char[] data + * and creates a fixed-length string at that location using the length info from the struct. + *

+ * The string struct is found in a couple of different ways: + *

    + *
  • References from an instruction (see markupStaticStructRefsInFunction) + *
  • Iterating through data segments and making educated guesses (see markupDataSegmentStructs) + *
+ * Some char[] data is only referenced from Golang string structs that exist temporarily + * in registers after being set by an instruction that statically references the char[] data, + * and an instruction that statically contains the length. (see tryCreateInlineString) + *

+ * Because slice structures can look like string structs, possible string struct locations are also + * examined for slice-ness. When marking a struct as a slice instead of as a string, the data + * pointed to by the slice is not marked up because there is no information about the size of the + * elements that the slice points to. + */ +public class GolangStringAnalyzer extends AbstractAnalyzer { + private final static String NAME = "Golang String Analyzer"; + private final static String DESCRIPTION = + "Finds and labels Go string structures that have been referenced from a function."; + + private GolangStringAnalyzerOptions analyzerOptions = new GolangStringAnalyzerOptions(); + private GoRttiMapper goBinary; + private MarkupSession markupSession; + private GoParamStorageAllocator storageAllocator; + + public GolangStringAnalyzer() { + super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); + setPriority(AnalysisPriority.REFERENCE_ANALYSIS.after()); + setDefaultEnablement(true); + } + + @Override + public void registerOptions(Options options, Program program_notused) { + analyzerOptions.registerOptions(options); + } + + @Override + public void optionsChanged(Options options, Program program_notused) { + analyzerOptions.optionsChanged(options); + } + + @Override + public boolean canAnalyze(Program program) { + return GoRttiMapper.isGolangProgram(program); + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + + goBinary = GoRttiMapper.getSharedGoBinary(program, monitor); + if (goBinary == null) { + Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper"); + return false; + } + markupSession = goBinary.createMarkupSession(monitor); + storageAllocator = goBinary.newStorageAllocator(); + + try { + goBinary.initTypeInfoIfNeeded(monitor); + + markupStaticStructRefsInFunctions(set, monitor); + + if (analyzerOptions.markupDataSegmentStructs) { + markupDataSegmentStructs(monitor); + } + } + catch (IOException e) { + Msg.error(this, "Golang analysis failure", e); + } + + return true; + } + + private static boolean alignStartOfSet(AddressSet set, int align) { + while (!set.isEmpty()) { + Address addr = set.getMinAddress(); + int mod = (int) (addr.getOffset() % align); + if (mod == 0) { + return true; + } + AddressRange range = set.getFirstRange(); + addr = addr.add(align - mod - 1); + set.deleteFromMin(range.contains(addr) ? addr : range.getMaxAddress()); + } + return false; + } + + private void markupStaticStructRefsInFunctions(AddressSetView set, TaskMonitor monitor) + throws IOException, CancelledException { + monitor = new UnknownProgressWrappingTaskMonitor(monitor); + monitor.initialize(1, "Searching for Golang structure references in functions"); + + FunctionManager funcManager = goBinary.getProgram().getFunctionManager(); + + for (Function function : funcManager.getFunctions(set, true)) { + monitor.checkCancelled(); + markupStaticStructRefsInFunction(function, monitor); + } + } + + private void markupStaticStructRefsInFunction(Function function, TaskMonitor monitor) + throws IOException, CancelledException { + + ReferenceManager refManager = goBinary.getProgram().getReferenceManager(); + Listing listing = goBinary.getProgram().getListing(); + + AddressSet stringDataRange = new AddressSet(goBinary.getStringDataRange()); + AddressSetView validStructRange = goBinary.getStringStructRange(); + AddressSetView funcBody = function.getBody(); + for (Reference ref : refManager.getReferenceIterator(function.getEntryPoint())) { + monitor.increment(); + if (!funcBody.contains(ref.getFromAddress())) { + // limit the reference iterator to the function body + break; + } + Address addr = ref.getToAddress(); + + if (ref.getReferenceType() != RefType.DATA) { + continue; + } + if (validStructRange.contains(addr) && canCreateStructAt(addr)) { + if (tryCreateStruct(stringDataRange, addr) != null) { + continue; + } + if (!stringDataRange.contains(addr)) { + continue; + } + + Instruction instr = listing.getInstructionContaining(ref.getFromAddress()); + GoString inlineStr = tryCreateInlineString(funcBody, stringDataRange, addr, instr, + this::isValidStringData); + if (inlineStr != null) { + // just markup the char data, there is no struct body + inlineStr.additionalMarkup(markupSession); + } + } + } + } + + private GoString tryCreateInlineString(AddressSetView funcBody, AddressSet stringDataRange, + Address stringDataAddr, Instruction instr1, Predicate stringContentValidator) { + // Precondition: instr1 has a ref to a location that could be the chars of a + // string (stringDataAddr). + // The logic matches on 2 consecutive instructions that load 2 consecutive go param + // storage allocator registers (ex AX, then BX), because we know that a go string struct + // can fit into 2 registers when passed to a function + // This needs to be improved to handle initializing a stack parameter. + + Instruction instr2 = instr1.getNext(); + if (instr2 == null || !funcBody.contains(instr2.getAddress())) { + return null; + } + + Register strAddrReg = getRegisterFromInstr(instr1); + Register strLenReg = storageAllocator.getNextIntParamRegister(strAddrReg); + if (strLenReg != null && strLenReg.contains(getRegisterFromInstr(instr2))) { + // the first register was from the set of param passing registers, and we now know + // what the second register must be to match the pattern + long strLen = getScalarFromInstr(instr2); + try { + GoString str = GoString.createInlineString(goBinary, stringDataAddr, strLen); + if (str.isValidInlineString(stringDataRange, stringContentValidator)) { + return str; + } + } + catch (IOException e) { + // fail + } + } + return null; + } + + private Register getRegisterFromInstr(Instruction instr) { + if (instr.getNumOperands() != 2 || instr.getOperandType(0) != OperandType.REGISTER) { + return null; + } + return (Register) instr.getOpObjects(0)[0]; + } + + private long getScalarFromInstr(Instruction instr) { + if (instr.getNumOperands() != 2) { + return -1; + } + Object operand1Obj0 = instr.getOpObjects(1)[0]; + return instr.getOperandType(1) == OperandType.SCALAR && operand1Obj0 instanceof Scalar s + ? s.getUnsignedValue() + : -1; + } + + private void markupDataSegmentStructs(TaskMonitor monitor) + throws IOException, CancelledException { + + AddressSet structDataRange = new AddressSet(goBinary.getStringStructRange()); + structDataRange.delete(markupSession.getMarkedupAddresses()); + + AddressSet stringDataRange = new AddressSet(goBinary.getStringDataRange()); + + long initAddrCount = structDataRange.getNumAddresses(); + monitor.initialize(initAddrCount, + "Searching for Golang strings & structures in data segments"); + + int stringCount = 0; + int sliceCount = 0; + + int align = goBinary.getPtrSize(); + while (alignStartOfSet(structDataRange, align)) { + monitor.setProgress(initAddrCount - structDataRange.getNumAddresses()); + monitor.checkCancelled(); + + Address addr = structDataRange.getMinAddress(); + if (!canCreateStructAt(addr)) { + Data data = goBinary.getProgram().getListing().getDataContaining(addr); + if (data != null) { + structDataRange.deleteFromMin(data.getMaxAddress()); + } + continue; + } + + structDataRange.deleteFromMin(addr); + + Object newObj = tryCreateStruct(stringDataRange, addr); + if (newObj != null) { + structDataRange.deleteFromMin(goBinary.getMaxAddressOfStructure(newObj)); + stringCount += newObj instanceof GoString ? 1 : 0; + sliceCount += newObj instanceof GoSlice ? 1 : 0; + monitor.setMessage("Searching for Golang strings & slices in data segments: %d+%d" + .formatted(stringCount, sliceCount)); + } + } + } + + private Object tryCreateStruct(AddressSet stringDataRange, Address addr) + throws IOException, CancelledException { + // test to see if its a slice first because strings can kinda look like slices (a pointer + // then a length field). + Object newObj = tryReadSliceStruct(addr); + if (newObj == null) { + newObj = tryReadStringStruct(stringDataRange, addr); + } + if (newObj != null) { + boolean doMarkup = analyzerOptions.markupSliceStructs || !(newObj instanceof GoSlice); + if (doMarkup) { + markupSession.markup(newObj, false); + if (newObj instanceof GoString goStr) { + stringDataRange.delete(goStr.getStringDataRange()); + } + } + } + return newObj; + } + + private GoString tryReadStringStruct(AddressSetView stringDataRange, Address addr) { + try { + GoString goString = goBinary.readStructure(GoString.class, addr); + if (goString.isValid(stringDataRange, this::isValidStringData)) { + return goString; + } + } + catch (IOException e) { + // ignore, skip + } + return null; + } + + private boolean isValidStringData(String s) { + // naive test that ensures that the candidate string doesn't have 'garbage' characters + // TODO: this should be wired into the string model logic + return (s != null) && !s.codePoints().anyMatch(this::isBadCodePoint); + } + + private boolean isBadCodePoint(int codePoint) { + return codePoint == StringUtilities.UNICODE_REPLACEMENT || + (0 <= codePoint && codePoint < 32) && !(codePoint == '\n' || codePoint == '\t'); + } + + private GoSlice tryReadSliceStruct(Address addr) { + try { + GoSlice slice = goBinary.readStructure(GoSlice.class, addr); + if (slice.getLen() != 0 && slice.isFull() && slice.isValid()) { + return slice; + } + } + catch (IOException e) { + // ignore, skip + } + return null; + } + + private boolean canCreateStructAt(Address addr) { + Data data = goBinary.getProgram().getListing().getDataContaining(addr); + return data == null || !data.isDefined() || Undefined.isUndefined(data.getDataType()) || + (data.getDataType() instanceof Pointer ptr && ptr.getDataType() == null); + } + + //------------------------------------------------------------------------------------------- + private static class GolangStringAnalyzerOptions { + static final String MARKUP_SLICES_OPTIONNAME = "Markup slices"; + static final String MARKUP_SLICES_DESC = "Markup things that look like slices."; + + static final String MARKUP_STRUCTS_IN_DATA_OPTIONNAME = "Search data segments"; + static final String MARKUP_STRUCTS_IN_DATA_DESC = + "Search for strings and slices in data segments."; + + boolean markupSliceStructs = true; + boolean markupDataSegmentStructs = true; + + void registerOptions(Options options) { + options.registerOption(GolangStringAnalyzerOptions.MARKUP_SLICES_OPTIONNAME, + markupSliceStructs, null, GolangStringAnalyzerOptions.MARKUP_SLICES_DESC); + options.registerOption(GolangStringAnalyzerOptions.MARKUP_STRUCTS_IN_DATA_OPTIONNAME, + markupDataSegmentStructs, null, + GolangStringAnalyzerOptions.MARKUP_STRUCTS_IN_DATA_DESC); + } + + void optionsChanged(Options options) { + markupDataSegmentStructs = + options.getBoolean(GolangStringAnalyzerOptions.MARKUP_STRUCTS_IN_DATA_OPTIONNAME, + markupDataSegmentStructs); + markupSliceStructs = options.getBoolean( + GolangStringAnalyzerOptions.MARKUP_SLICES_OPTIONNAME, markupSliceStructs); + + } + + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java index 6f3907884d..04de4bbc0d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java @@ -15,30 +15,43 @@ */ package ghidra.app.plugin.core.analysis; +import static ghidra.app.util.bin.format.golang.GoConstants.*; +import static java.util.stream.Collectors.*; +import static java.util.stream.StreamSupport.*; + import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.util.*; +import java.util.Map.Entry; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import generic.jar.ResourceFile; +import ghidra.app.cmd.comments.SetCommentCmd; +import ghidra.app.cmd.function.ApplyFunctionSignatureCmd; import ghidra.app.services.*; import ghidra.app.util.MemoryBlockUtils; -import ghidra.app.util.bin.format.dwarf4.DWARFUtil; -import ghidra.app.util.bin.format.elf.info.ElfInfoItem.ItemWithAddress; import ghidra.app.util.bin.format.golang.*; import ghidra.app.util.bin.format.golang.rtti.*; +import ghidra.app.util.bin.format.golang.rtti.types.GoMethod.GoMethodInfo; +import ghidra.app.util.bin.format.golang.rtti.types.GoType; import ghidra.app.util.bin.format.golang.structmapping.MarkupSession; import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler; +import ghidra.framework.cmd.BackgroundCommand; +import ghidra.framework.model.DomainObject; import ghidra.framework.options.Options; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.address.*; import ghidra.program.model.data.*; -import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.lang.Register; import ghidra.program.model.listing.*; import ghidra.program.model.listing.Function.FunctionUpdateType; import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.pcode.HighFunctionDBUtil; import ghidra.program.model.symbol.*; +import ghidra.program.util.*; +import ghidra.program.util.SymbolicPropogator.Value; import ghidra.util.Msg; import ghidra.util.NumericUtilities; import ghidra.util.exception.*; @@ -48,7 +61,8 @@ import ghidra.xml.XmlParseException; import utilities.util.FileUtilities; /** - * Analyzes Golang binaries for RTTI and function symbol information. + * Analyzes Golang binaries for RTTI and function symbol information by following references from + * the root GoModuleData instance. */ public class GolangSymbolAnalyzer extends AbstractAnalyzer { @@ -57,58 +71,86 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { Analyze Golang binaries for RTTI and function symbols. 'Apply Data Archives' and 'Shared Return Calls' analyzers should be disabled \ for best results."""; - private final static String ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME = - "ARTIFICIAL.runtime.zerobase"; + private static final String ANALYZED_FLAG_OPTION_NAME = "Golang Analyzed"; private GolangAnalyzerOptions analyzerOptions = new GolangAnalyzerOptions(); + private GoRttiMapper goBinary; + private MarkupSession markupSession; + private AutoAnalysisManager aam; + private long lastTxId = -1; + public GolangSymbolAnalyzer() { super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); setPriority(AnalysisPriority.FORMAT_ANALYSIS.after().after()); setDefaultEnablement(true); } + @Override + public boolean canAnalyze(Program program) { + return GoRttiMapper.isGolangProgram(program); + } + @Override public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException { + long txId = program.getCurrentTransactionInfo().getID(); + if (txId == lastTxId) { + // Only run once per analysis session - as denoted by being in the same transaction + return true; + } + lastTxId = txId; + + if (isAlreadyImported(program)) { + Msg.info(this, "Golang analysis already performed, skipping."); + return false; + } + monitor.setMessage("Golang symbol analyzer"); - try (GoRttiMapper goBinary = GoRttiMapper.getMapperFor(program, log)) { - if (goBinary == null) { - Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper"); - return false; - } - goBinary.init(monitor); - goBinary.discoverGoTypes(monitor); + aam = AutoAnalysisManager.getAnalysisManager(program); - UnknownProgressWrappingTaskMonitor upwtm = - new UnknownProgressWrappingTaskMonitor(monitor, 100); - upwtm.initialize(0); - upwtm.setMessage("Marking up Golang RTTI structures"); + goBinary = GoRttiMapper.getSharedGoBinary(program, monitor); + if (goBinary == null) { + Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper"); + return false; + } - MarkupSession markupSession = goBinary.createMarkupSession(upwtm); + try { + goBinary.initTypeInfoIfNeeded(monitor); + goBinary.initMethodInfoIfNeeded(); + + markupSession = goBinary.createMarkupSession(monitor); GoModuledata firstModule = goBinary.getFirstModule(); if (firstModule != null) { - markupSession.labelStructure(firstModule, "firstmoduledata"); + markupSession.labelStructure(firstModule, "firstmoduledata", null); markupSession.markup(firstModule, false); } - markupWellknownSymbols(goBinary, markupSession); - setupProgramContext(goBinary, markupSession); + markupWellknownSymbols(); + setupProgramContext(); goBinary.recoverDataTypes(monitor); - markupGoFunctions(goBinary, markupSession); - fixupNoReturnFuncs(program); - markupMiscInfoStructs(program); + markupGoFunctions(monitor); + if (analyzerOptions.fixupDuffFunctions) { + fixDuffFunctions(); + } + + if (analyzerOptions.propagateRtti) { + aam.schedule(new PropagateRttiBackgroundCommand(goBinary), + AnalysisPriority.REFERENCE_ANALYSIS.after().priority()); + } if (analyzerOptions.createBootstrapDatatypeArchive) { - createBootstrapGDT(goBinary, program, monitor); + createBootstrapGDT(monitor); } + + program.getOptions(Program.PROGRAM_INFO).setBoolean(ANALYZED_FLAG_OPTION_NAME, true); + return true; } catch (IOException e) { Msg.error(this, "Golang analysis failure", e); } - - return true; + return false; } @Override @@ -116,6 +158,12 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { options.registerOption(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME, analyzerOptions.createBootstrapDatatypeArchive, null, GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_DESC); + options.registerOption(GolangAnalyzerOptions.OUTPUT_SOURCE_INFO_OPTIONNAME, + analyzerOptions.outputSourceInfo, null, GolangAnalyzerOptions.OUTPUT_SOURCE_INFO_DESC); + options.registerOption(GolangAnalyzerOptions.FIXUP_DUFF_FUNCS_OPTIONNAME, + analyzerOptions.fixupDuffFunctions, null, GolangAnalyzerOptions.FIXUP_DUFF_FUNCS_DESC); + options.registerOption(GolangAnalyzerOptions.PROP_RTTI_OPTIONNAME, + analyzerOptions.propagateRtti, null, GolangAnalyzerOptions.PROP_RTTI_DESC); } @Override @@ -123,143 +171,219 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { analyzerOptions.createBootstrapDatatypeArchive = options.getBoolean(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME, analyzerOptions.createBootstrapDatatypeArchive); + analyzerOptions.outputSourceInfo = options.getBoolean( + GolangAnalyzerOptions.OUTPUT_SOURCE_INFO_OPTIONNAME, analyzerOptions.outputSourceInfo); } - private void markupWellknownSymbols(GoRttiMapper goBinary, MarkupSession session) - throws IOException { + private void markupWellknownSymbols() throws IOException { Program program = goBinary.getProgram(); Symbol g0 = SymbolUtilities.getUniqueSymbol(program, "runtime.g0"); Structure gStruct = goBinary.getGhidraDataType("runtime.g", Structure.class); if (g0 != null && gStruct != null) { - session.markupAddressIfUndefined(g0.getAddress(), gStruct); + markupSession.markupAddressIfUndefined(g0.getAddress(), gStruct); } Symbol m0 = SymbolUtilities.getUniqueSymbol(program, "runtime.m0"); Structure mStruct = goBinary.getGhidraDataType("runtime.m", Structure.class); if (m0 != null && mStruct != null) { - session.markupAddressIfUndefined(m0.getAddress(), mStruct); + markupSession.markupAddressIfUndefined(m0.getAddress(), mStruct); } } - private void markupGoFunctions(GoRttiMapper goBinary, MarkupSession markupSession) - throws IOException { - for (GoFuncData funcdata : goBinary.getAllFunctions()) { - String funcname = SymbolUtilities.replaceInvalidChars(funcdata.getName(), true); - markupSession.createFunctionIfMissing(funcname, funcdata.getFuncAddress()); - } - try { - fixDuffFunctions(goBinary, markupSession); - } - catch (InvalidInputException | DuplicateNameException e) { - Msg.error(this, "Error configuring duff functions", e); + private void markupGoFunctions(TaskMonitor monitor) throws IOException, CancelledException { + Set noreturnFuncNames = readNoReturnFuncNames(); + int noreturnFuncCount = 0; + int functionSignatureFromBootstrap = 0; + int functionSignatureFromMethod = 0; + int partialFunctionSignatureFromMethod = 0; + + List funcs = goBinary.getAllFunctions(); + monitor.initialize(funcs.size(), "Fixing golang function signatures"); + for (GoFuncData funcdata : funcs) { + monitor.increment(); + + Address funcAddr = funcdata.getFuncAddress(); + GoSymbolName funcSymbolNameInfo = funcdata.getSymbolName(); + String funcname = + SymbolUtilities.replaceInvalidChars(funcSymbolNameInfo.getSymbolName(), true); + Namespace funcns = funcSymbolNameInfo.getSymbolNamespace(goBinary.getProgram()); + + if ("go:buildid".equals(funcSymbolNameInfo.getSymbolName())) { + // this funcdata entry is a bogus element that points to the go buildid string. skip + continue; + } + + Function func = markupSession.createFunctionIfMissing(funcname, funcns, funcAddr); + if (func == null) { + continue; + } + + boolean existingFuncSignature = + func.getParameterCount() != 0 || !Undefined.isUndefined(func.getReturnType()); + + markupSession.appendComment(func, "Golang function info: ", + AddressAnnotatedStringHandler.createAddressAnnotationString( + funcdata.getStructureContext().getStructureAddress(), + "Flags: %s, ID: %s".formatted(funcdata.getFlags(), funcdata.getFuncIDEnum()))); + + if (!funcSymbolNameInfo.getSymbolName().equals(funcname)) { + markupSession.appendComment(func, "Golang original name: ", + funcSymbolNameInfo.getSymbolName()); + } + + GoSourceFileInfo sfi = null; + if (analyzerOptions.outputSourceInfo && (sfi = funcdata.getSourceFileInfo()) != null) { + markupSession.appendComment(func, "Golang source: ", sfi.getDescription()); + } + + if (funcdata.getFlags().isEmpty() /* dont try to get arg info for ASM funcs*/) { + markupSession.appendComment(func, null, + "Golang recovered signature: " + funcdata.recoverFunctionSignature()); + } + + // Try to get a function definition signature from: + // 1) Methods (with full info) attached to a type that point to this func + // 2) Signature found in the bootstrap gdt file (matched by name) + // 3) Artificial partial func signatures constructed from the go type method that points here + GoMethodInfo boundMethod = funcdata.findMethodInfo(); + FunctionDefinition funcdef = boundMethod != null ? boundMethod.getSignature() : null; + FunctionDefinition partialFuncdef = + boundMethod != null && funcdef == null ? boundMethod.getPartialSignature() : null; + + if (funcdef == null) { + funcdef = goBinary.getBootstrapFunctionDefintion(funcname); + if (funcdef != null) { + functionSignatureFromBootstrap++; + } + } + else { + functionSignatureFromMethod++; + } + if (funcdef == null && partialFuncdef != null && !existingFuncSignature) { + // use partial funcdef that only has a receiver 'this' parameter + funcdef = partialFuncdef; + partialFunctionSignatureFromMethod++; + } + if (funcdef != null) { + ApplyFunctionSignatureCmd cmd = + new ApplyFunctionSignatureCmd(funcAddr, funcdef, SourceType.ANALYSIS); + cmd.applyTo(goBinary.getProgram()); + try { + GoFunctionFixup.fixupFunction(func, goBinary.getGolangVersion()); + } + catch (DuplicateNameException | InvalidInputException e) { + Msg.error(this, "Failed to fix function custom storage", e); + } + } + + if (noreturnFuncNames.contains(funcname)) { + if (!func.hasNoReturn()) { + func.setNoReturn(true); + noreturnFuncCount++; + } + } + + if (boundMethod != null) { + String addrAnnotation = AddressAnnotatedStringHandler.createAddressAnnotationString( + boundMethod.getType().getStructureContext().getStructureAddress(), + boundMethod.getType().getName()); + String methodComment = "Golang method in type %s%s: ".formatted(addrAnnotation, + partialFuncdef != null ? " [partial]" : ""); + markupSession.appendComment(func, "", + methodComment + (partialFuncdef != null ? partialFuncdef : funcdef)); + } + } + + Msg.info(this, "Marked %d golang funcs as NoReturn".formatted(noreturnFuncCount)); + Msg.info(this, "Fixed %d golang function signatures from runtime snapshot signatures" + .formatted(functionSignatureFromBootstrap)); + Msg.info(this, "Fixed %d golang function signatures from method info" + .formatted(functionSignatureFromMethod)); + Msg.info(this, "Fixed %d golang function signatures from partial method info" + .formatted(partialFunctionSignatureFromMethod)); } /** * Fixes the function signature of the runtime.duffzero and runtime.duffcopy functions. *

* The alternate duff-ified entry points haven't been discovered yet, so the information - * set to the main function entry point will be propagated at a later time to the alternate - * entry points by the GolangDuffFixupAnalyzer. - * - * @param goBinary the golang binary - * @param session {@link MarkupSession} - * @throws InvalidInputException if error assigning the function signature - * @throws DuplicateNameException if error assigning the function signature + * set to the main function entry point will be propagated at a later time by the + * FixupDuffAlternateEntryPointsBackgroundCommand. */ - private void fixDuffFunctions(GoRttiMapper goBinary, MarkupSession session) - throws InvalidInputException, DuplicateNameException { + private void fixDuffFunctions() { Program program = goBinary.getProgram(); GoRegisterInfo regInfo = goBinary.getRegInfo(); DataType voidPtr = program.getDataTypeManager().getPointer(VoidDataType.dataType); DataType uintDT = goBinary.getTypeOrDefault("uint", DataType.class, - AbstractUnsignedIntegerDataType.getUnsignedDataType(goBinary.getPtrSize(), null)); - + AbstractIntegerDataType.getUnsignedDataType(goBinary.getPtrSize(), null)); + GoFuncData duffzeroFuncdata = goBinary.getFunctionByName("runtime.duffzero"); Function duffzeroFunc = duffzeroFuncdata != null ? program.getFunctionManager().getFunctionAt(duffzeroFuncdata.getFuncAddress()) : null; - PrototypeModel duffzeroCC = goBinary.getDuffzeroCallingConvention(); - if (duffzeroFunc != null && duffzeroCC != null) { - // NOTE: some duffzero funcs need a zero value supplied to them via a register set - // by the caller. (depending on the arch) The duffzero calling convention defined - // by the callspec should take care of this by defining that register as the second - // storage location. Otherwise, the callspec will only have a single storage - // location defined. - boolean needZeroValueParam = regInfo.getZeroRegister() == null; - List params = new ArrayList<>(); - params.add(new ParameterImpl("dest", voidPtr, program)); - if (needZeroValueParam) { - params.add(new ParameterImpl("zeroValue", uintDT, program)); + if (duffzeroFunc != null && + goBinary.hasCallingConvention(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME)) { + try { + // NOTE: some duffzero funcs need a zero value supplied to them via a register set + // by the caller. (depending on the arch) The duffzero calling convention defined + // by the callspec should take care of this by defining that register as the second + // storage location. Otherwise, the callspec will only have a single storage + // location defined. + boolean needZeroValueParam = regInfo.getZeroRegister() == null; + List params = new ArrayList<>(); + params.add(new ParameterImpl("dest", voidPtr, program)); + if (needZeroValueParam) { + params.add(new ParameterImpl("zeroValue", uintDT, program)); + } + + duffzeroFunc.updateFunction(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME, + new ReturnParameterImpl(VoidDataType.dataType, program), params, + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); + + markupSession.appendComment(duffzeroFunc, null, + "Golang special function: duffzero"); + + aam.schedule(new FixupDuffAlternateEntryPointsBackgroundCommand(duffzeroFuncdata, + duffzeroFunc), AnalysisPriority.FUNCTION_ANALYSIS.after().priority()); + } + catch (InvalidInputException | DuplicateNameException e) { + Msg.error(this, "Failed to update main duffzero function", e); } - duffzeroFunc.updateFunction(duffzeroCC.getName(), - new ReturnParameterImpl(VoidDataType.dataType, program), params, - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, - SourceType.ANALYSIS); + GoFuncData duffcopyFuncdata = goBinary.getFunctionByName("runtime.duffcopy"); + Function duffcopyFunc = duffcopyFuncdata != null + ? program.getFunctionManager().getFunctionAt(duffcopyFuncdata.getFuncAddress()) + : null; + if (duffcopyFuncdata != null && + goBinary.hasCallingConvention(GOLANG_DUFFCOPY_CALLINGCONVENTION_NAME)) { + try { + List params = List.of(new ParameterImpl("dest", voidPtr, program), + new ParameterImpl("src", voidPtr, program)); + duffcopyFunc.updateFunction(GOLANG_DUFFCOPY_CALLINGCONVENTION_NAME, + new ReturnParameterImpl(VoidDataType.dataType, program), params, + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); - DWARFUtil.appendComment(program, duffzeroFunc.getEntryPoint(), CodeUnit.PLATE_COMMENT, - "Golang special function: ", "duffzero", "\n"); - } + markupSession.appendComment(duffcopyFunc, null, + "Golang special function: duffcopy"); - GoFuncData duffcopyFuncdata = goBinary.getFunctionByName("runtime.duffcopy"); - Function duffcopyFunc = duffcopyFuncdata != null - ? program.getFunctionManager().getFunctionAt(duffcopyFuncdata.getFuncAddress()) - : null; - PrototypeModel duffcopyCC = goBinary.getDuffcopyCallingConvention(); - if (duffcopyFuncdata != null && duffcopyCC != null) { - List params = List.of( - new ParameterImpl("dest", voidPtr, program), - new ParameterImpl("src", voidPtr, program)); - duffcopyFunc.updateFunction(duffcopyCC.getName(), - new ReturnParameterImpl(VoidDataType.dataType, program), params, - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); - - DWARFUtil.appendComment(program, duffcopyFunc.getEntryPoint(), CodeUnit.PLATE_COMMENT, - "Golang special function: ", "duffcopy", "\n"); - } - - } - - private void markupMiscInfoStructs(Program program) { - // this also adds "golang" info to program properties - - ItemWithAddress wrappedBuildInfo = GoBuildInfo.findBuildInfo(program); - if (wrappedBuildInfo != null && program.getListing() - .isUndefined(wrappedBuildInfo.address(), wrappedBuildInfo.address())) { - // this will mostly be PE binaries that don't have Elf markup magic stuff - wrappedBuildInfo.item().markupProgram(program, wrappedBuildInfo.address()); - } - ItemWithAddress wrappedPeBuildId = PEGoBuildId.findBuildId(program); - if (wrappedPeBuildId != null && program.getListing() - .isUndefined(wrappedPeBuildId.address(), wrappedPeBuildId.address())) { - // HACK to handle golang hack: check if a function symbol was laid down at the location - // of the buildId string. If true, convert it to a plain label - Symbol[] buildIdSymbols = - program.getSymbolTable().getSymbols(wrappedPeBuildId.address()); - for (Symbol sym : buildIdSymbols) { - if (sym.getSymbolType() == SymbolType.FUNCTION) { - String symName = sym.getName(); - sym.delete(); - try { - program.getSymbolTable() - .createLabel(wrappedPeBuildId.address(), symName, - SourceType.IMPORTED); - } - catch (InvalidInputException e) { - // ignore - } - break; + aam.schedule( + new FixupDuffAlternateEntryPointsBackgroundCommand(duffcopyFuncdata, + duffcopyFunc), + AnalysisPriority.FUNCTION_ANALYSIS.after().priority()); + } + catch (InvalidInputException | DuplicateNameException e) { + Msg.error(this, "Failed to update main duffcopy function", e); } } - wrappedPeBuildId.item().markupProgram(program, wrappedPeBuildId.address()); } + } - private void fixupNoReturnFuncs(Program program) { + private Set readNoReturnFuncNames() { Set noreturnFuncnames = new HashSet<>(); - + Program program = goBinary.getProgram(); try { for (ResourceFile file : NonReturningFunctionNames.findDataFiles(program)) { FileUtilities.getLines(file) @@ -272,31 +396,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { catch (IOException | XmlParseException e) { Msg.error(this, "Failed to read Golang noreturn func data file", e); } - - int count = 0; - SymbolTable symbolTable = program.getSymbolTable(); - for (Symbol symbol : symbolTable.getPrimarySymbolIterator(true)) { - String name = symbol.getName(false); - if (symbol.isExternal() /* typically not an issue with golang */ - || !noreturnFuncnames.contains(name)) { - continue; - } - - Function functionAt = program.getFunctionManager().getFunctionAt(symbol.getAddress()); - if (functionAt == null) { - continue; - } - if (!functionAt.hasNoReturn()) { - - functionAt.setNoReturn(true); - - program.getBookmarkManager() - .setBookmark(symbol.getAddress(), BookmarkType.ANALYSIS, - "Non-Returning Function", "Non-Returning Golang Function Identified"); - count++; - } - } - Msg.info(this, "Marked %d golang funcs as NoReturn".formatted(count)); + return noreturnFuncnames; } private Address createFakeContextMemory(Program program, long len) { @@ -310,8 +410,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { return newMB.getStart(); } - private void setupProgramContext(GoRttiMapper goBinary, MarkupSession session) - throws IOException { + private void setupProgramContext() throws IOException { Program program = goBinary.getProgram(); GoRegisterInfo goRegInfo = goBinary.getRegInfo(); @@ -349,19 +448,18 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { ? NumericUtilities.getUnsignedAlignedValue(mStruct.getLength(), alignment) : 0; - Address contextMemoryAddr = sizeNeeded > 0 - ? createFakeContextMemory(program, sizeNeeded) - : null; + Address contextMemoryAddr = + sizeNeeded > 0 ? createFakeContextMemory(program, sizeNeeded) : null; if (zerobase == null) { - session.labelAddress(contextMemoryAddr.add(zerobaseSymbol), - ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME); + markupSession.labelAddress(contextMemoryAddr.add(zerobaseSymbol), + GoRttiMapper.ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME); } if (gStruct != null) { Address gAddr = contextMemoryAddr.add(gStructOffset); - session.markupAddressIfUndefined(gAddr, gStruct); - session.labelAddress(gAddr, "CURRENT_G"); + markupSession.markupAddressIfUndefined(gAddr, gStruct); + markupSession.labelAddress(gAddr, "CURRENT_G"); Register currentGoroutineReg = goRegInfo.getCurrentGoroutineRegister(); if (currentGoroutineReg != null && txtMemblock != null) { @@ -380,66 +478,290 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { } if (mStruct != null) { Address mAddr = contextMemoryAddr.add(mStructOffset); - session.markupAddressIfUndefined(mAddr, mStruct); + markupSession.markupAddressIfUndefined(mAddr, mStruct); } } - private void createBootstrapGDT(GoRttiMapper goBinary, Program program, - TaskMonitor monitor) throws IOException { + private void createBootstrapGDT(TaskMonitor monitor) throws IOException, CancelledException { + Program program = goBinary.getProgram(); GoVer goVer = goBinary.getGolangVersion(); String osName = GoRttiMapper.getGolangOSString(program); - String gdtFilename = - GoRttiMapper.getGDTFilename(goVer, goBinary.getPtrSize(), osName); - gdtFilename = - gdtFilename.replace(".gdt", "_%d.gdt".formatted(System.currentTimeMillis())); + String gdtFilename = GoRttiMapper.getGDTFilename(goVer, goBinary.getPtrSize(), osName); + gdtFilename = gdtFilename.replace(".gdt", "_%d.gdt".formatted(System.currentTimeMillis())); File gdt = new File(System.getProperty("user.home"), gdtFilename); - goBinary.exportTypesToGDT(gdt, monitor); + goBinary.exportTypesToGDT(gdt, analyzerOptions.createRuntimeSnapshotDatatypeArchive, + monitor); Msg.info(this, "Golang bootstrap GDT created: " + gdt); } - @Override - public void analysisEnded(Program program) { - } - - @Override - public boolean canAnalyze(Program program) { - return GoConstants.GOLANG_CSPEC_NAME.equals( - program.getCompilerSpec().getCompilerSpecDescription().getCompilerSpecName()); - } - - @Override - public boolean getDefaultEnablement(Program program) { - return true; - } - - private static Address getArtificalZerobaseAddress(Program program) { - Symbol zerobaseSym = - SymbolUtilities.getUniqueSymbol(program, ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME); - return zerobaseSym != null ? zerobaseSym.getAddress() : null; - } - + //-------------------------------------------------------------------------------------------- /** - * Return the address of the golang zerobase symbol, or an artificial substitute. - *

- * The zerobase symbol is used as the location of parameters that are zero-length. - * - * @param prog {@link Program} - * @return {@link Address} of the runtime.zerobase, or artificial substitute + * A background command that runs later, it copies the function signature information from the + * main entry point of the duff function to any unnamed functions that are within the footprint + * of the main function. */ - public static Address getZerobaseAddress(Program prog) { - Symbol zerobaseSym = SymbolUtilities.getUniqueSymbol(prog, "runtime.zerobase"); - Address zerobaseAddr = zerobaseSym != null - ? zerobaseSym.getAddress() - : getArtificalZerobaseAddress(prog); - if (zerobaseAddr == null) { - zerobaseAddr = prog.getImageBase().getAddressSpace().getMinAddress(); // ICKY HACK - Msg.warn(GoFunctionFixup.class, - "Unable to find Golang runtime.zerobase, using " + zerobaseAddr); + private static class FixupDuffAlternateEntryPointsBackgroundCommand extends BackgroundCommand { + + private Function duffFunc; + private GoFuncData funcData; + + public FixupDuffAlternateEntryPointsBackgroundCommand(GoFuncData funcData, + Function duffFunc) { + this.funcData = funcData; + this.duffFunc = duffFunc; } - return zerobaseAddr; + + @Override + public boolean applyTo(DomainObject obj, TaskMonitor monitor) { + String ccName = duffFunc.getCallingConventionName(); + Namespace funcNS = duffFunc.getParentNamespace(); + AddressSet funcBody = new AddressSet(funcData.getBody()); + Program program = duffFunc.getProgram(); + String duffComment = program.getListing() + .getCodeUnitAt(duffFunc.getEntryPoint()) + .getComment(CodeUnit.PLATE_COMMENT); + monitor.setMessage("Fixing alternate duffzero/duffcopy entry points"); + for (FunctionIterator funcIt = + program.getFunctionManager().getFunctions(funcBody, true); funcIt.hasNext();) { + Function func = funcIt.next(); + if (!FunctionUtility.isDefaultFunctionName(func)) { + continue; + } + try { + func.setName(duffFunc.getName() + "_" + func.getEntryPoint(), + SourceType.ANALYSIS); + func.setParentNamespace(funcNS); + func.updateFunction(ccName, duffFunc.getReturn(), + Arrays.asList(duffFunc.getParameters()), + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); + if (duffComment != null && !duffComment.isBlank()) { + new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment) + .applyTo(program); + } + } + catch (DuplicateNameException | InvalidInputException + | CircularDependencyException e) { + Msg.error(GolangSymbolAnalyzer.class, "Error updating duff functions", e); + } + } + return true; + } + } //-------------------------------------------------------------------------------------------- + /** + * A background command that runs after reference analysis, it applies functions signature + * overrides to callsites that have a RTTI type parameter that return a specialized + * type instead of a void*. + */ + private static class PropagateRttiBackgroundCommand extends BackgroundCommand { + record RttiFuncInfo(GoSymbolName funcName, int rttiParamIndex, + java.util.function.Function returnTypeMapper) { + + public RttiFuncInfo(String funcName, int rttiParamIndex, + java.util.function.Function returnTypeMapper) { + this(GoSymbolName.parse(funcName), rttiParamIndex, returnTypeMapper); + } + } + + record CallSiteInfo(Reference ref, Function callingFunc, Function calledFunc, + Register register, + java.util.function.Function returnTypeMapper) {} + + private GoRttiMapper goBinary; + private MarkupSession markupSession; + int totalCallsiteCount; + int fixedCallsiteCount; + int unfixedCallsiteCount; + int callingFunctionCount; + + public PropagateRttiBackgroundCommand(GoRttiMapper goBinary) { + this.goBinary = goBinary; + } + + @Override + public boolean applyTo(DomainObject obj, TaskMonitor monitor) { + if (goBinary.newStorageAllocator().isAbi0Mode()) { + // If abi0 mode, don't even bother because currently only handles rtti passed via + // register. + return true; + } + try { + this.markupSession = goBinary.createMarkupSession(monitor); + Set>> callsiteInfo = + getInformationAboutCallsites(monitor); + + monitor.initialize(totalCallsiteCount, "Propagating RTTI from callsites"); + for (Entry> callsite : callsiteInfo) { + monitor.checkCancelled(); + fixupRttiCallsitesInFunc(callsite.getKey(), callsite.getValue(), monitor); + } + Msg.info(this, "Golang RTTI callsite fixup info (total/updated/skipped): %d/%d/%d" + .formatted(totalCallsiteCount, fixedCallsiteCount, unfixedCallsiteCount)); + return true; + } + catch (CancelledException e) { + return false; + } + + } + + private void fixupRttiCallsitesInFunc(Function callingFunc, List callsites, + TaskMonitor monitor) throws CancelledException { + Program program = goBinary.getProgram(); + + monitor.setMessage("Propagating RTTI from callsites in %s@%s" + .formatted(callingFunc.getName(), callingFunc.getEntryPoint())); + + ContextEvaluator eval = new ConstantPropagationContextEvaluator(monitor, true); + SymbolicPropogator symEval = new SymbolicPropogator(program); + symEval.flowConstants(callingFunc.getEntryPoint(), callingFunc.getBody(), eval, true, + monitor); + + monitor.setMessage("Propagating RTTI from callsites in %s@%s" + .formatted(callingFunc.getName(), callingFunc.getEntryPoint())); + + for (CallSiteInfo callsite : callsites) { + monitor.increment(); + + Value val = + symEval.getRegisterValue(callsite.ref.getFromAddress(), callsite.register); + if (val == null || val.isRegisterRelativeValue()) { + //Msg.warn(this, "Failed to get RTTI param reg value: " + callsite); + unfixedCallsiteCount++; + continue; + } + + long goTypeOffset = val.getValue(); + try { + GoType goType = goBinary.getCachedGoType(goTypeOffset); + if (goType == null) { + // if it was previously not discovered (usually closure anon types), also mark it up + goType = goBinary.getGoType(goTypeOffset); + markupSession.markup(goType, false); + } + DataType newReturnType = + goType != null ? callsite.returnTypeMapper.apply(goType) : null; + if (newReturnType != null) { + // Create a funcdef for this call site, where the return value is a + // specific glang type instead of the void* it was before. + FunctionDefinitionDataType signature = + new FunctionDefinitionDataType(callsite.calledFunc, true); + signature.setReturnType(newReturnType); + try { + HighFunctionDBUtil.writeOverride(callsite.callingFunc, + callsite.ref.getFromAddress(), signature); + } + catch (InvalidInputException e) { + Msg.error(this, "Failed to override call", e); + } + fixedCallsiteCount++; + } + } + catch (IOException e) { + Msg.error(this, "Failed to override call", e); + } + } + } + + Set>> getInformationAboutCallsites(TaskMonitor monitor) { + TaskMonitor upwtm = new UnknownProgressWrappingTaskMonitor(monitor); + upwtm.initialize(1, "Finding callsites with RTTI"); + + BiConsumer> getReferencesToRttiFuncWithMonitor = + (rfi, c) -> getReferencesToRttiFunc(rfi, c, upwtm); + + //@formatter:off + Map> result = List.of( + new RttiFuncInfo("runtime.newobject", 0, this::getReturnTypeForNewObjectFunc), + new RttiFuncInfo("runtime.makeslice", 0, this::getReturnTypeForSliceFunc), + new RttiFuncInfo("runtime.growslice", 4, this::getReturnTypeForSliceFunc), // won't work unless func signature for growslice is applied, which is a future "todo" + new RttiFuncInfo("runtime.makeslicecopy", 0, this::getReturnTypeForSliceFunc)) + .stream() + .mapMulti(getReferencesToRttiFuncWithMonitor) + .collect(groupingBy(csi -> csi.callingFunc)); + //@formatter:on + + callingFunctionCount = result.size(); + return result.entrySet(); + } + + private void getReferencesToRttiFunc(RttiFuncInfo rfi, Consumer consumer, + TaskMonitor monitor) { + Program program = goBinary.getProgram(); + FunctionManager funcMgr = program.getFunctionManager(); + ReferenceManager refMgr = program.getReferenceManager(); + + Function func = rfi.funcName.getFunction(program); + if (func != null) { + // TODO: improve this to handle stack values. Currently only supports values in + // registers. + List callRegs = getRegistersForParameter(func, rfi.rttiParamIndex); + if (callRegs == null || callRegs.size() != 1) { + return; + } + + Register paramReg = callRegs.get(0); + + stream(refMgr.getReferencesTo(func.getEntryPoint()).spliterator(), false) // + .filter(ref -> !monitor.isCancelled() && ref != null && + ref.getReferenceType().isCall()) + .map(ref -> new CallSiteInfo(ref, + funcMgr.getFunctionContaining(ref.getFromAddress()), func, paramReg, + rfi.returnTypeMapper)) + .forEach(consumer.andThen(_unused -> { + monitor.incrementProgress(); + totalCallsiteCount++; + })); + } + } + + private DataType getReturnTypeForNewObjectFunc(GoType goType) { + try { + DataTypeManager dtm = goBinary.getDTM(); + DataType dt = goBinary.getRecoveredType(goType); + return dtm.getPointer(dt); + } + catch (IOException e) { + return null; + } + } + + private DataType getReturnTypeForSliceFunc(GoType goType) { + try { + GoType sliceGoType = goBinary.findGoType("[]" + goType.getNameWithPackageString()); + DataType dt = sliceGoType != null ? goBinary.getRecoveredType(sliceGoType) : null; + return dt; + } + catch (IOException e) { + return null; + } + } + + private List getRegistersForParameter(Function func, int paramIndex) { + GoParamStorageAllocator storageAllocator = goBinary.newStorageAllocator(); + Parameter[] params = func.getParameters(); + if (params.length == 0 && paramIndex == 0) { + // TODO: this is a hack to handle lack of func param info for built-in runtime alloc methods + // This will not be needed once param info for the alloc methods is applied before + // we get to this step. + // This only works with the rtti funcs that pass the gotype ref in first param + return storageAllocator.getRegistersFor(goBinary.getUintptrDT()); + } + for (int i = 0; i < params.length; i++) { + DataType paramDT = params[i].getDataType(); + List regs = storageAllocator.getRegistersFor(paramDT); + if (i == paramIndex) { + return regs; + } + } + return List.of(); + } + + } + //-------------------------------------------------------------------------------------------- + private static class GolangAnalyzerOptions { static final String CREATE_BOOTSTRAP_GDT_OPTIONNAME = "Create Bootstrap GDT"; static final String CREATE_BOOTSTRAP_GDT_DESC = """ @@ -450,5 +772,37 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { be called golang_MajorVer.MinorVer_XXbit_osname.NNNNNNNNN.gdt, where NNNNNN \ is a timestamp."""; boolean createBootstrapDatatypeArchive; + + boolean createRuntimeSnapshotDatatypeArchive; + + static final String OUTPUT_SOURCE_INFO_OPTIONNAME = "Output Source Info"; + static final String OUTPUT_SOURCE_INFO_DESC = """ + Add "source_file_name:line_number" information to functions."""; + boolean outputSourceInfo = true; + + static final String FIXUP_DUFF_FUNCS_OPTIONNAME = "Fixup Duff Functions"; + static final String FIXUP_DUFF_FUNCS_DESC = """ + Copies information from the runtime.duffzero and runtime.duffcopy functions to \ + the alternate duff entry points that are discovered during later analysis."""; + boolean fixupDuffFunctions = true; + + static final String PROP_RTTI_OPTIONNAME = "Propagate RTTI"; + static final String PROP_RTTI_DESC = """ + Override the function signature of calls to some built-in Golang allocator \ + functions (runtime.newobject(), runtime.makeslice(), etc) that have a constant \ + reference to a Golang type record to have a return type of that specific Golang \ + type."""; + boolean propagateRtti = true; + } + + /** + * Returns true if DWARF has already been imported into the specified program. + * + * @param program {@link Program} to check + * @return true if DWARF has already been imported, false if not yet + */ + public static boolean isAlreadyImported(Program program) { + Options options = program.getOptions(Program.PROGRAM_INFO); + return options.getBoolean(ANALYZED_FLAG_OPTION_NAME, false); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/TransientProgramProperties.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/TransientProgramProperties.java new file mode 100644 index 0000000000..6a666432fc --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/TransientProgramProperties.java @@ -0,0 +1,193 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.plugin.core.analysis; + +import java.io.Closeable; +import java.util.*; +import java.util.Map.Entry; +import java.util.function.Supplier; + +import ghidra.formats.gfilesystem.FSUtilities; +import ghidra.framework.model.DomainObject; +import ghidra.framework.model.DomainObjectClosedListener; +import ghidra.program.model.listing.Program; + +/** + * Mechanism to associate values with a currently open program. Values will be released when + * the program is closed, or when the current analysis session is finished. + *

+ * Values that are linked to things in a Program that are subject to be reverted during a + * transaction roll-back should probably not be stored in a PROGRAM scoped property. (example: + * DataTypes, CodeUnits, etc) ANALYSIS_SESSION scoped properties are protected from rollback + * by the active transaction that is held during the session. + * + */ +public class TransientProgramProperties { + + private static final Map properties = new HashMap<>(); + + public enum SCOPE { + PROGRAM, // value will be released when the program is closed + ANALYSIS_SESSION // value will be released when the current analysis session is finished + } + + /** + * A checked {@link Supplier} + * + * @param type of result + * @param type of exception thrown + */ + public interface PropertyValueSupplier { + T get() throws E; + } + + /** + * Returns true if the specified property is present. + * + * @param program {@link Program} + * @param key property key + * @return boolean true if property is present. + */ + public static synchronized boolean hasProperty(Program program, Object key) { + PerProgramProperties perProgramProps = properties.get(program); + return perProgramProps != null ? perProgramProps.props.containsKey(key) : false; + } + + /** + * Returns a property value that has been associated with the specified program. + *

+ * If the property wasn't present, the {@link PropertyValueSupplier} will be used to + * create the value and associate it with the program. + * + * @param type of the property value. If the property value is {@link Closeable}, it + * will be {@link Closeable#close() closed} when released. + * @param type of the exception the supplier throws + * @param program {@link Program} + * @param key property key + * @param scope {@link SCOPE} lifetime of property. If an analysis session is NOT active, + * requesting {@link SCOPE#ANALYSIS_SESSION} will throw an IllegalArgumentException. If the + * requested scope does not match the scope of the already existing value, an + * IllegalArgumentException will be thrown. + * @param clazz type of the property value + * @param supplier {@link PropertyValueSupplier} callback that will create the property + * value if it is not present + * @return property value + * @throws IllegalArgumentException if scope == ANALYSIS_SESSION and there is no active analysis + * session, OR, if the requested scope does not match the scope used to an earlier call for + * the same property + * @throws E same exception type that the supplier throws + */ + public static synchronized T getProperty(Program program, Object key, + SCOPE scope, Class clazz, PropertyValueSupplier supplier) throws E { + if (scope == SCOPE.ANALYSIS_SESSION && + (!AutoAnalysisManager.hasAutoAnalysisManager(program) || + !AutoAnalysisManager.getAnalysisManager(program).isAnalyzing())) { + throw new IllegalArgumentException("No active analysis session"); + } + + PerProgramProperties perProgramProps = + properties.computeIfAbsent(program, PerProgramProperties::new); + + Property property = perProgramProps.props.get(key); + if (property == null) { + T supplierVal = supplier.get(); + if (supplierVal == null) { + return null; + } + property = new Property(key, supplierVal, scope); + perProgramProps.addProperty(key, supplierVal, scope); + } + if (property.scope != scope) { + throw new IllegalArgumentException("Mismatched Program property scope"); + } + return clazz.isInstance(property.value) ? clazz.cast(property.value) : null; + } + + /** + * Release all properties for the specified program. + * + * @param program {@link Program} + */ + public static synchronized void removeProgramProperties(Program program) { + PerProgramProperties removedProps = properties.remove(program); + if (removedProps != null) { + removedProps.close(); + } + } + + //--------------------------------------------------------------------------------------------- + private record Property(Object key, Object value, SCOPE scope) implements Closeable { + @Override + public void close() { + if (value instanceof Closeable c) { + FSUtilities.uncheckedClose(c, null); + } + } + } + + private static class PerProgramProperties + implements DomainObjectClosedListener, AutoAnalysisManagerListener, Closeable { + final Program program; + final Map props; + boolean aamListenerAdded; + + PerProgramProperties(Program program) { + this.program = program; + this.props = new HashMap<>(); + program.addCloseListener(this); + } + + @Override + public void domainObjectClosed(DomainObject dobj) { + removeProgramProperties(program); + } + + @Override + public void analysisEnded(AutoAnalysisManager manager, boolean isCancelled) { + for (Iterator> it = props.entrySet().iterator(); it + .hasNext();) { + Entry entry = it.next(); + Property prop = entry.getValue(); + if (prop.scope == SCOPE.ANALYSIS_SESSION) { + it.remove(); + prop.close(); + } + } + } + + @Override + public void close() { + props.forEach((key, prop) -> prop.close()); + props.clear(); + program.removeCloseListener(this); + if (aamListenerAdded) { + AutoAnalysisManager.getAnalysisManager(program).removeListener(this); + } + } + + private void addProperty(Object key, Object value, SCOPE scope) { + if (scope == SCOPE.ANALYSIS_SESSION) { + if (!aamListenerAdded) { + AutoAnalysisManager.getAnalysisManager(program).addListener(this); + aamListenerAdded = true; + } + } + props.put(key, new Property(key, value, scope)); + } + + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java index 6d01b34a4a..64182f2aaf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java @@ -60,9 +60,9 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener { "a newly discovered starting symbol, provided the user hasn't manually moved."; private static final String DEFAULT_STARTING_SYMBOLS = - "main, WinMain, libc_start_main, WinMainStartup, main.main, start, entry"; + "main.main, main, WinMain, libc_start_main, WinMainStartup, start, entry"; - public static enum StartLocationType { + public enum StartLocationType { LOWEST_ADDRESS("Lowest Address"), LOWEST_CODE_BLOCK("Lowest Code Block Address"), SYMBOL_NAME("Preferred Symbol Name"), @@ -70,7 +70,7 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener { private String label; - private StartLocationType(String label) { + StartLocationType(String label) { this.label = label; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java index 728999bebe..3bf7b5ce62 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java @@ -644,8 +644,10 @@ public class BinaryReader { * Reads an integer array of nElements * starting at the current index and then increments the current * index by SIZEOF_INT * nElements. + * + * @param nElements number of elements to read * @return the integer array starting at the current index - * @exception IOException if an I/O error occurs + * @throws IOException if an I/O error occurs */ public int[] readNextIntArray(int nElements) throws IOException { int[] i = readIntArray(currentIndex, nElements); @@ -657,8 +659,10 @@ public class BinaryReader { * Reads a long array of nElements * starting at the current index and then increments the current * index by SIZEOF_LONG * nElements. + * + * @param nElements number of elements to read * @return the long array starting at the current index - * @exception IOException if an I/O error occurs + * @throws IOException if an I/O error occurs */ public long[] readNextLongArray(int nElements) throws IOException { long[] l = readLongArray(currentIndex, nElements); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java index ad6950a104..17f9afc763 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java @@ -28,6 +28,7 @@ import ghidra.app.util.bin.format.dwarf4.encoding.*; import ghidra.app.util.bin.format.dwarf4.expression.*; import ghidra.app.util.bin.format.dwarf4.next.DWARFProgram; import ghidra.util.Msg; +import ghidra.util.NumericUtilities; /** * DIEAggregate groups related {@link DebugInfoEntry} records together in a single interface @@ -724,7 +725,8 @@ public class DIEAggregate { } // Check to see if this is a base address entry - if (beginning == -1) { + if (beginning == -1 || + (pointerSize == 4 && beginning == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG)) { baseAddressOffset = ending; continue; } @@ -913,7 +915,8 @@ public class DIEAggregate { } // Check to see if this is a base address entry - if (beginning == -1) { + if (beginning == -1 || + (pointerSize == 4 && beginning == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG)) { baseAddress = ending; continue; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/DWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/DWARFFunctionFixup.java index 9839e054b4..6977fb40a4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/DWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/DWARFFunctionFixup.java @@ -15,11 +15,11 @@ */ package ghidra.app.util.bin.format.dwarf4.funcfixup; +import java.io.Closeable; import java.util.List; import ghidra.app.util.bin.format.dwarf4.DWARFException; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; -import ghidra.program.model.listing.Function; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ExtensionPoint; @@ -32,6 +32,14 @@ import ghidra.util.classfinder.ExtensionPoint; *

* Fixups are found using {@link ClassSearcher}, and their class names must end * in "DWARFFunctionFixup" (see ExtensionPoint.manifest). + *

+ * Instance lifetime: + *

+ * New instances are not shared between programs or analysis sessions, but will be re-used to + * handle the various functions found in a single binary. + *

+ * If the implementation also implements {@link Closeable}, it will be called when the fixup + * is no longer needed. */ public interface DWARFFunctionFixup extends ExtensionPoint { public static final int PRIORITY_NORMAL_EARLY = 4000; @@ -46,9 +54,8 @@ public interface DWARFFunctionFixup extends ExtensionPoint { * a {@link DWARFException}. * * @param dfunc {@link DWARFFunction} info read from DWARF about the function - * @param gfunc the Ghidra {@link Function} that will receive the DWARF information */ - void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException; + void fixupDWARFFunction(DWARFFunction dfunc) throws DWARFException; /** * Return a list of all current {@link DWARFFunctionFixup fixups} found in the classpath diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/OutputParamCheckDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/OutputParamCheckDWARFFunctionFixup.java index 6532a36d2d..9123ce17e0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/OutputParamCheckDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/OutputParamCheckDWARFFunctionFixup.java @@ -17,7 +17,6 @@ package ghidra.app.util.bin.format.dwarf4.funcfixup; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable; -import ghidra.program.model.listing.Function; import ghidra.util.Msg; import ghidra.util.classfinder.ExtensionPointProperties; @@ -29,13 +28,13 @@ import ghidra.util.classfinder.ExtensionPointProperties; public class OutputParamCheckDWARFFunctionFixup implements DWARFFunctionFixup { @Override - public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + public void fixupDWARFFunction(DWARFFunction dfunc) { // Complain about parameters that are marked as 'output' that haven't been handled by // some other fixup, as we don't know what to do with them. for (DWARFVariable dvar : dfunc.params) { if (dvar.isOutputParameter && dvar.isMissingStorage()) { - Msg.warn(this, String.format("Unsupported output parameter for %s@%s: %s", - gfunc.getName(), gfunc.getEntryPoint(), dvar.name.getName())); + Msg.warn(this, "Unsupported output parameter for %s@%s" + .formatted(dfunc.name.getName(), dfunc.address)); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamNameDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamNameDWARFFunctionFixup.java index 485c4ddb87..69607d4cd9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamNameDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamNameDWARFFunctionFixup.java @@ -16,7 +16,6 @@ package ghidra.app.util.bin.format.dwarf4.funcfixup; import ghidra.app.util.bin.format.dwarf4.next.*; -import ghidra.program.model.listing.Function; import ghidra.util.classfinder.ExtensionPointProperties; /** @@ -26,13 +25,13 @@ import ghidra.util.classfinder.ExtensionPointProperties; public class ParamNameDWARFFunctionFixup implements DWARFFunctionFixup { @Override - public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + public void fixupDWARFFunction(DWARFFunction dfunc) { // Fix any dups among the parameters, to-be-added-local vars, and already present local vars NameDeduper nameDeduper = new NameDeduper(); nameDeduper.addReservedNames(dfunc.getAllParamNames()); nameDeduper.addReservedNames(dfunc.getAllLocalVariableNames()); - nameDeduper.addUsedNames(dfunc.getNonParamSymbolNames(gfunc)); + nameDeduper.addUsedNames(dfunc.getNonParamSymbolNames()); for (DWARFVariable param : dfunc.params) { String newName = nameDeduper.getUniqueName(param.name.getName()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamSpillDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamSpillDWARFFunctionFixup.java index 3c08c7c021..6667d82072 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamSpillDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamSpillDWARFFunctionFixup.java @@ -17,7 +17,6 @@ package ghidra.app.util.bin.format.dwarf4.funcfixup; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable; -import ghidra.program.model.listing.Function; import ghidra.util.classfinder.ExtensionPointProperties; /** @@ -31,7 +30,7 @@ import ghidra.util.classfinder.ExtensionPointProperties; public class ParamSpillDWARFFunctionFixup implements DWARFFunctionFixup { @Override - public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + public void fixupDWARFFunction(DWARFFunction dfunc) { for (DWARFVariable param : dfunc.params) { if (!param.isStackStorage()) { continue; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/RustDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/RustDWARFFunctionFixup.java index 0818db1470..e117f8d914 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/RustDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/RustDWARFFunctionFixup.java @@ -19,7 +19,6 @@ import ghidra.app.util.bin.format.dwarf4.DIEAggregate; import ghidra.app.util.bin.format.dwarf4.DWARFException; import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; -import ghidra.program.model.listing.Function; import ghidra.util.classfinder.ExtensionPointProperties; /** @@ -29,7 +28,7 @@ import ghidra.util.classfinder.ExtensionPointProperties; public class RustDWARFFunctionFixup implements DWARFFunctionFixup { @Override - public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException { + public void fixupDWARFFunction(DWARFFunction dfunc) throws DWARFException { DIEAggregate diea = dfunc.diea; int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage(); if (cuLang == DWARFSourceLanguage.DW_LANG_Rust && dfunc.params.isEmpty()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/SanityCheckDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/SanityCheckDWARFFunctionFixup.java index c6276d70c3..41a2fbc5f9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/SanityCheckDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/SanityCheckDWARFFunctionFixup.java @@ -18,7 +18,6 @@ package ghidra.app.util.bin.format.dwarf4.funcfixup; import ghidra.app.util.bin.format.dwarf4.DWARFException; import ghidra.app.util.bin.format.dwarf4.attribs.DWARFAttributeValue; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; -import ghidra.program.model.listing.Function; import ghidra.util.Msg; import ghidra.util.classfinder.ExtensionPointProperties; @@ -29,15 +28,13 @@ import ghidra.util.classfinder.ExtensionPointProperties; public class SanityCheckDWARFFunctionFixup implements DWARFFunctionFixup, DWARFAttributeValue { @Override - public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException { + public void fixupDWARFFunction(DWARFFunction dfunc) throws DWARFException { // if there were no defined parameters and we had problems decoding local variables, // don't force the method to have an empty param signature because there are other // issues afoot. if (dfunc.params.isEmpty() && dfunc.localVarErrors) { - Msg.error(this, - String.format( - "Inconsistent function signature information, leaving undefined: %s@%s", - gfunc.getName(), gfunc.getEntryPoint())); + Msg.error(this, "Inconsistent function signature information, leaving undefined: %s@%s" + .formatted(dfunc.name.getName(), dfunc.address)); throw new DWARFException("Failed sanity check"); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/StorageVerificationDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/StorageVerificationDWARFFunctionFixup.java index 4e0a97e9f5..0d167b5571 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/StorageVerificationDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/StorageVerificationDWARFFunctionFixup.java @@ -18,7 +18,6 @@ package ghidra.app.util.bin.format.dwarf4.funcfixup; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode; import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable; -import ghidra.program.model.listing.Function; import ghidra.util.classfinder.ExtensionPointProperties; /** @@ -32,7 +31,7 @@ import ghidra.util.classfinder.ExtensionPointProperties; public class StorageVerificationDWARFFunctionFixup implements DWARFFunctionFixup { @Override - public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + public void fixupDWARFFunction(DWARFFunction dfunc) { boolean storageIsGood = true; for (DWARFVariable param : dfunc.params) { if (param.isMissingStorage() && !param.isZeroByte()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ThisCallingConventionDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ThisCallingConventionDWARFFunctionFixup.java index 0d739babb7..62e17682e3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ThisCallingConventionDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ThisCallingConventionDWARFFunctionFixup.java @@ -17,7 +17,7 @@ package ghidra.app.util.bin.format.dwarf4.funcfixup; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable; -import ghidra.program.model.data.GenericCallingConvention; +import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.listing.Function; import ghidra.util.Msg; import ghidra.util.classfinder.ExtensionPointProperties; @@ -29,8 +29,8 @@ import ghidra.util.classfinder.ExtensionPointProperties; public class ThisCallingConventionDWARFFunctionFixup implements DWARFFunctionFixup { @Override - public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { - if (dfunc.params.isEmpty() || dfunc.callingConvention != null) { + public void fixupDWARFFunction(DWARFFunction dfunc) { + if (dfunc.params.isEmpty() || dfunc.callingConventionName != null) { // if someone else set calling convention, don't override it return; } @@ -39,14 +39,13 @@ public class ThisCallingConventionDWARFFunctionFixup implements DWARFFunctionFix if (firstParam.isThis) { if (!firstParam.name.isAnon() && !Function.THIS_PARAM_NAME.equals(firstParam.name.getOriginalName())) { - Msg.error(this, - String.format("WARNING: Renaming %s to %s in function %s@%s", - firstParam.name.getName(), Function.THIS_PARAM_NAME, gfunc.getName(), - gfunc.getEntryPoint())); + Msg.warn(this, "Renaming %s to %s in function %s@%s".formatted( + firstParam.name.getName(), Function.THIS_PARAM_NAME, dfunc.name.getName(), + dfunc.address)); } firstParam.name = firstParam.name.replaceName(Function.THIS_PARAM_NAME, Function.THIS_PARAM_NAME); - dfunc.callingConvention = GenericCallingConvention.thiscall; + dfunc.callingConventionName = CompilerSpec.CALLING_CONVENTION_thiscall; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataInstanceHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataInstanceHelper.java index 05e76383e7..2b03830c7f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataInstanceHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataInstanceHelper.java @@ -26,12 +26,18 @@ import ghidra.program.model.listing.*; public class DWARFDataInstanceHelper { private Program program; private Listing listing; + private boolean allowTruncating = true; public DWARFDataInstanceHelper(Program program) { this.program = program; this.listing = program.getListing(); } + public DWARFDataInstanceHelper setAllowTruncating(boolean b) { + this.allowTruncating = b; + return this; + } + private boolean isArrayDataTypeCompatibleWithExistingData(Array arrayDT, Data existingData) { DataType existingDataDT = existingData.getBaseDataType(); @@ -60,10 +66,13 @@ public class DWARFDataInstanceHelper { return false; } - if (arrayDT.getLength() <= existingData.getLength()) { - // if proposed array is smaller than in-memory array: ok + if (arrayDT.getLength() == existingData.getLength()) { return true; } + if (arrayDT.getLength() < existingData.getLength()) { + // if proposed array is smaller than in-memory array + return allowTruncating; + } // if proposed array is longer than in-memory array, check if there is only // undefined data following the in-memory array @@ -105,7 +114,8 @@ public class DWARFDataInstanceHelper { return false; } } - return true; + boolean isTruncating = structDT.getLength() < existingData.getLength(); + return !isTruncating || allowTruncating; } private boolean isPointerDataTypeCompatibleWithExistingData(Pointer pdt, Data existingData) { @@ -119,22 +129,27 @@ public class DWARFDataInstanceHelper { private boolean isSimpleDataTypeCompatibleWithExistingData(DataType simpleDT, Data existingData) { - // dataType will only be a base data type, not a typedef - + // simpleDT will be int, char, float, or string data types + + boolean isSameLen = isSameLen(simpleDT, existingData); DataType existingDT = existingData.getBaseDataType(); + if (simpleDT instanceof CharDataType && existingDT instanceof StringDataType) { // char overwriting a string + return isSameLen || allowTruncating; + } + + if (Undefined.isUndefined(existingDT) && isSameLen) { + // some type overwriting an undefined return true; } - if (!simpleDT.getClass().isInstance(existingDT)) { - return false; - } - int dataTypeLen = simpleDT.getLength(); - if (dataTypeLen > 0 && dataTypeLen != existingData.getLength()) { - return false; - } - return true; + return simpleDT.getClass().isInstance(existingDT) && isSameLen; + } + + private boolean isSameLen(DataType dt, Data existingData) { + return existingData.getLength() == dt.getLength() || + (dt instanceof Dynamic dyDT && dyDT.canSpecifyLength()); } private boolean isEnumDataTypeCompatibleWithExistingData(Enum enumDT, Data existingData) { @@ -192,8 +207,14 @@ public class DWARFDataInstanceHelper { return true; } - Data data = listing.getDataAt(address); - return data == null || isDataTypeCompatibleWithExistingData(dataType, data); + Data data = listing.getDataContaining(address); + if (data == null) { + return false; // will only get null if something is really screwed up + } + if (!data.getMinAddress().equals(address)) { + return false; // was pointing to something in the middle of an existing data instance + } + return isDataTypeCompatibleWithExistingData(dataType, data); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporter.java index 93b759a0e9..94f76c2fc7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporter.java @@ -28,6 +28,7 @@ import ghidra.app.util.DataTypeNamingUtil; import ghidra.app.util.bin.format.dwarf4.*; import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute; import ghidra.app.util.bin.format.dwarf4.encoding.DWARFEndianity; +import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; import ghidra.app.util.bin.format.golang.rtti.types.GoKind; import ghidra.program.database.DatabaseObject; @@ -197,10 +198,7 @@ public class DWARFDataTypeImporter { break; case DW_TAG_subroutine_type: - result = makeDataTypeForFunctionDefinition(diea, true); - break; - case DW_TAG_subprogram: - result = makeDataTypeForFunctionDefinition(diea, false); + result = makeDataTypeForFunctionDefinition(diea); break; default: Msg.warn(this, "Unsupported datatype in die: " + diea.toString()); @@ -265,19 +263,36 @@ public class DWARFDataTypeImporter { /* * Returns a Ghidra {@link FunctionDefinition} datatype built using the info from the DWARF die. *

- * Function types may be assigned "mangled" names with parameter type info if the name - * is already used or if this is an unnamed function defintion. - *

- * Can accept DW_TAG_subprogram, DW_TAG_subroutine_type DIEAs. - *

- * The logic of this impl is the same as {@link DWARFDataTypeManager#createFunctionDefinitionDataType(DIEAggregate)} - * but the impls can't be shared without excessive over-engineering. + * Function types may be assigned "mangled" names with parameter type info if this is an + * unnamed function definition. */ - private DWARFDataType makeDataTypeForFunctionDefinition(DIEAggregate diea, - boolean mangleAnonFuncNames) throws IOException, DWARFExpressionException { + private DWARFDataType makeDataTypeForFunctionDefinition(DIEAggregate diea) + throws IOException, DWARFExpressionException { DWARFNameInfo dni = prog.getName(diea); + // push an empty funcdef data type into the lookup cache to prevent recursive loops + FunctionDefinitionDataType funcDef = + new FunctionDefinitionDataType(dni.getParentCP(), dni.getName(), dataTypeManager); + DataType dtToAdd = funcDef; + + int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage(); + if (cuLang == DWARFSourceLanguage.DW_LANG_Go) { + if (diea.hasAttribute(DW_AT_byte_size)) { + // if the funcdef has a bytesize attribute, we should convert this data type to a ptr + long ptrSize = diea.getUnsignedLong(DW_AT_byte_size, -1); + if (ptrSize == dataTypeManager.getDataOrganization().getPointerSize()) { + ptrSize = -1;// use default pointer size + } + dtToAdd = dwarfDTM.getPtrTo(dtToAdd, (int) ptrSize); + } + // golang funcdefs are ptr->ptr->funcs + dtToAdd = dwarfDTM.getPtrTo(dtToAdd, -1); + } + + DWARFDataType result = new DWARFDataType(dtToAdd, dni, diea.getOffset()); + recordTempDataType(result); // prevents recursive type-lookups for this funcdef + DWARFDataType returnType = getDataType(diea.getTypeRef(), voidDDT); boolean foundThisParam = false; @@ -288,15 +303,6 @@ public class DWARFDataTypeImporter { DWARFDataType paramDT = getDataType(paramDIEA.getTypeRef(), null); DataType dt = fixupDataTypeInconsistencies(paramDT); - if (dt == null && DWARFUtil.isPointerDataType(paramDIEA.getTypeRef())) { - // Hack to handle Golang self-referencing func defs. - Msg.error(this, - "Error resolving parameter data type, probable recursive definition, replacing with void*: " + - dni.getName()); - Msg.debug(this, "Problem funcDef: " + diea.toString()); - Msg.debug(this, "Problem param: " + paramDIEA); - dt = dwarfDTM.getPtrTo(dwarfDTM.getVoidType()); - } if (dt == null || dt.getLength() <= 0) { Msg.error(this, "Bad function parameter type for " + dni.asCategoryPath()); return null; @@ -308,8 +314,6 @@ public class DWARFDataTypeImporter { foundThisParam |= DWARFUtil.isThisParam(paramDIEA); } - FunctionDefinitionDataType funcDef = - new FunctionDefinitionDataType(dni.getParentCP(), dni.getName(), dataTypeManager); funcDef.setReturnType(returnType.dataType); funcDef.setNoReturn(diea.getBool(DW_AT_noreturn, false)); funcDef.setArguments(params.toArray(new ParameterDefinition[params.size()])); @@ -327,9 +331,8 @@ public class DWARFDataTypeImporter { } } - if (dni.isAnon() && mangleAnonFuncNames) { - String mangledName = - DataTypeNamingUtil.setMangledAnonymousFunctionName(funcDef); + if (dni.isAnon()) { + String mangledName = DataTypeNamingUtil.setMangledAnonymousFunctionName(funcDef); dni = dni.replaceName(mangledName, dni.getOriginalName()); } @@ -339,17 +342,7 @@ public class DWARFDataTypeImporter { updateMapping(origPD.getDataType(), newPD.getDataType()); } - DataType dtToAdd = funcDef; - if (diea.hasAttribute(DW_AT_byte_size)) { - // if the funcdef has a bytesize attribute, we should convert this data type to a ptr - long ptrSize = diea.getUnsignedLong(DW_AT_byte_size, -1); - if (ptrSize == dataTypeManager.getDataOrganization().getPointerSize()) { - ptrSize = -1;// use default pointer size - } - dtToAdd = dwarfDTM.getPtrTo(dtToAdd, (int) ptrSize); - } - - return new DWARFDataType(dtToAdd, dni, diea.getOffset()); + return result; } /** @@ -1301,11 +1294,16 @@ public class DWARFDataTypeImporter { boolean typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict( typedefDNI.asDataTypePath().getPath(), refdDT.dataType.getPathName()); - if (!typedefWithSameName && refdDT.dataType instanceof Pointer ptrDT && - ptrDT.getDataType() instanceof FunctionDefinition pointedToFuncDefDT) { - // hack to handle funcDefs that produce a ptr_to_funcdef instead of a funcdef type, which messes with name compare - typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict( - typedefDNI.asDataTypePath().getPath(), pointedToFuncDefDT.getPathName()); + if (!typedefWithSameName && refdDT.dataType instanceof Pointer ptrDT) { + if (ptrDT.getDataType() instanceof Pointer ptrptrDT) { + // handle double ptr-to-ptr-to-funcdef (golang typedefs to funcdefs) + ptrDT = ptrptrDT; + } + if (ptrDT.getDataType() instanceof FunctionDefinition pointedToFuncDefDT) { + // hack to handle funcDefs that produce a ptr_to_funcdef instead of a funcdef type, which messes with name compare + typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict( + typedefDNI.asDataTypePath().getPath(), pointedToFuncDefDT.getPathName()); + } } if (typedefWithSameName) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeManager.java index 8ac965790e..f5c1df950d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeManager.java @@ -588,10 +588,6 @@ public class DWARFDataTypeManager { int dtCountAfter = dataTypeManager.getDataTypeCount(true); importSummary.dataTypesAdded = (dtCountAfter - dtCountBefore); - - if (prog.getImportOptions().isCreateFuncSignatures()) { - importFuncSignatures(monitor); - } } private DIEAggregate getFuncDIEA(DIEAggregate diea) { @@ -632,49 +628,6 @@ public class DWARFDataTypeManager { return null; } - private void importFuncSignatures(TaskMonitor monitor) throws CancelledException { - - int dtCountBefore = dataTypeManager.getDataTypeCount(true); - - for (DIEAggregate diea : DIEAMonitoredIterator.iterable(prog, - "DWARF Import Function Signatures", monitor)) { - monitor.checkCancelled(); - try { - diea = getFuncDIEA(diea); - if (diea != null) { - DWARFNameInfo dni = prog.getName(diea); - DataType funcDefDT = createFunctionDefinitionDataType(diea, dni); - if (funcDefDT != null) { - // submit the temp 'impl' funcdef datatype to the DTM and get back a permanent - // db instance. - funcDefDT = dataTypeManager.addDataType(funcDefDT, - DataTypeConflictHandler.DEFAULT_HANDLER); - - // Look for the source info in the funcdef die and fall back to its - // parent's source info (handles auto-generated ctors and such) - addDataType(diea.getOffset(), funcDefDT, - DWARFSourceInfo.getSourceInfoWithFallbackToParent(diea)); - - Swing.runNow(Dummy.runnable()); - } - } - } - catch (OutOfMemoryError oom) { - throw oom; - } - catch (Throwable th) { - // Aggressively catch pretty much everything to allow the import to - // try to continue with the next compunit. - Msg.error(this, - "Error when processing DWARF information for DIE " + diea.getHexOffset(), th); - Msg.info(this, "DIE info:\n" + diea.toString()); - } - } - - int dtCountAfter = dataTypeManager.getDataTypeCount(true); - importSummary.funcSignaturesAdded = (dtCountAfter - dtCountBefore); - } - private boolean isDataType(DIEAggregate diea) { switch (diea.getTag()) { case DWARFTag.DW_TAG_base_type: @@ -704,16 +657,8 @@ public class DWARFDataTypeManager { * Creates a new {@link FunctionDefinitionDataType} from the specified {@link DIEAggregate} * using already known datatypes. *

- * The logic of this impl is the same as {@link DWARFDataTypeImporter#makeDataTypeForFunctionDefinition(DIEAggregate, boolean)} - * but the impls can't be shared without excessive over-engineering. - *

* This impl uses DataType's that have already been resolved and committed to the DTM, and * a cache mapping entry of the DWARF die -> DataType has been registered via {@link #addDataType(long, DataType, DWARFSourceInfo)}. - *

- * This approach is necessary because of speed issues that arise if the referred datatypes - * are created from scratch from the DWARF information and then have to go through a - * resolve() before being used in the FunctionDefinition. - * * * @param diea DWARF {@link DIEAggregate} that points to a subprogram or subroutine_type. * @param dni DWARF name info for the new function def diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunction.java index b2d547a2b6..c4769be4ca 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunction.java @@ -23,24 +23,26 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import ghidra.app.cmd.label.SetLabelPrimaryCmd; import ghidra.app.util.bin.format.dwarf4.*; import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; +import ghidra.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup; +import ghidra.program.database.function.OverlappingFunctionException; import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; import ghidra.program.model.data.*; -import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; import ghidra.program.model.symbol.*; -import ghidra.util.exception.DuplicateNameException; -import ghidra.util.exception.InvalidInputException; +import ghidra.util.Msg; +import ghidra.util.exception.*; /** * Represents a function that was read from DWARF information. */ public class DWARFFunction { - public enum CommitMode { - SKIP, FORMAL, STORAGE, - } + public enum CommitMode { SKIP, FORMAL, STORAGE, } public DIEAggregate diea; public DWARFNameInfo name; @@ -48,9 +50,9 @@ public class DWARFFunction { public Address address; public Address highAddress; public long frameBase; // TODO: change this to preserve the func's frameBase expr instead of value + public Function function; // ghidra function - public GenericCallingConvention callingConvention; - public PrototypeModel prototypeModel; + public String callingConventionName; public DWARFVariable retval; public List params = new ArrayList<>(); @@ -138,11 +140,7 @@ public class DWARFFunction { } public String getCallingConventionName() { - return prototypeModel != null - ? prototypeModel.getName() - : callingConvention != null - ? callingConvention.getDeclarationName() - : null; + return callingConventionName; } /** @@ -187,10 +185,10 @@ public class DWARFFunction { return false; } - public boolean hasConflictWithExistingLocalVariableStorage(DWARFVariable dvar, Function gfunc) + public boolean hasConflictWithExistingLocalVariableStorage(DWARFVariable dvar) throws InvalidInputException { VariableStorage newVarStorage = dvar.getVariableStorage(); - for (Variable existingVar : gfunc.getAllVariables()) { + for (Variable existingVar : function.getAllVariables()) { if (existingVar.getFirstUseOffset() == dvar.lexicalOffset && existingVar.getVariableStorage().intersects(newVarStorage)) { if ((existingVar instanceof LocalVariable) && @@ -217,20 +215,20 @@ public class DWARFFunction { .collect(Collectors.toList()); } - public List getExistingLocalVariableNames(Function gfunc) { - return Arrays.stream(gfunc.getLocalVariables()) + public List getExistingLocalVariableNames() { + return Arrays.stream(function.getLocalVariables()) .filter(var -> var.getName() != null && !Undefined.isUndefined(var.getDataType())) .map(var -> var.getName()) .collect(Collectors.toList()); } - - public List getNonParamSymbolNames(Function gfunc) { - SymbolIterator symbols = gfunc.getProgram().getSymbolTable().getSymbols(gfunc); + + public List getNonParamSymbolNames() { + SymbolIterator symbols = function.getProgram().getSymbolTable().getSymbols(function); return StreamSupport.stream(symbols.spliterator(), false) .filter(symbol -> symbol.getSymbolType() != SymbolType.PARAMETER) .map(Symbol::getName) .collect(Collectors.toList()); - } + } /** * Returns this function's parameters as a list of {@link Parameter} instances. @@ -238,63 +236,45 @@ public class DWARFFunction { * @param includeStorageDetail boolean flag, if true storage information will be included, if * false, VariableStorage.UNASSIGNED_STORAGE will be used * @return list of Parameters - * @throws InvalidInputException + * @throws InvalidInputException if bad information in param storage */ public List getParameters(boolean includeStorageDetail) throws InvalidInputException { List result = new ArrayList<>(); for (DWARFVariable dvar : params) { - result.add(dvar.asParameter(includeStorageDetail, getProgram().getGhidraProgram())); + result.add(dvar.asParameter(includeStorageDetail)); } return result; } /** - * Returns a {@link FunctionDefinition} that reflects this function's information. - * - * @return {@link FunctionDefinition} that reflects this function's information + * Returns the parameters of this function as {@link ParameterDefinition}s. + * + * @return array of {@link ParameterDefinition}s */ - public FunctionDefinition asFuncDef() { - List funcDefParams = new ArrayList<>(); - for (DWARFVariable param : params) { - funcDefParams.add(param.asParameterDef()); - } - - FunctionDefinitionDataType funcDef = - new FunctionDefinitionDataType(name.getParentCP(), name.getName(), - getProgram().getGhidraProgram().getDataTypeManager()); - funcDef.setReturnType(retval.type); - funcDef.setArguments(funcDefParams.toArray(ParameterDefinition[]::new)); - funcDef.setGenericCallingConvention( - Objects.requireNonNullElse(callingConvention, GenericCallingConvention.unknown)); - funcDef.setVarArgs(varArg); - - DWARFSourceInfo sourceInfo = null; - if (getProgram().getImportOptions().isOutputSourceLocationInfo() && - (sourceInfo = DWARFSourceInfo.create(diea)) != null) { - funcDef.setComment(sourceInfo.getDescriptionStr()); - } - - return funcDef; + public ParameterDefinition[] getParameterDefinitions() { + return params.stream() + .map(dvar -> new ParameterDefinitionImpl(dvar.name.getName(), dvar.type, null)) + .toArray(ParameterDefinition[]::new); } - public void commitLocalVariable(DWARFVariable dvar, Function gfunc) { - + public void commitLocalVariable(DWARFVariable dvar) { + VariableStorage varStorage = null; try { varStorage = dvar.getVariableStorage(); if (hasConflictWithParamStorage(dvar)) { - appendComment(gfunc.getEntryPoint(), CodeUnit.PLATE_COMMENT, - "Local variable %s[%s] conflicts with parameter, skipped.".formatted( - dvar.getDeclInfoString(), varStorage), + appendComment(function.getEntryPoint(), CodeUnit.PLATE_COMMENT, + "Local variable %s[%s] conflicts with parameter, skipped." + .formatted(dvar.getDeclInfoString(), varStorage), "\n"); return; } - if (hasConflictWithExistingLocalVariableStorage(dvar, gfunc)) { - appendComment(gfunc.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT, - "Local omitted variable %s[%s] scope starts here".formatted( - dvar.getDeclInfoString(), varStorage), + if (hasConflictWithExistingLocalVariableStorage(dvar)) { + appendComment(function.getEntryPoint().add(dvar.lexicalOffset), + CodeUnit.EOL_COMMENT, "Local omitted variable %s[%s] scope starts here" + .formatted(dvar.getDeclInfoString(), varStorage), "; "); return; } @@ -302,7 +282,7 @@ public class DWARFFunction { NameDeduper nameDeduper = new NameDeduper(); nameDeduper.addReservedNames(getAllLocalVariableNames()); nameDeduper.addUsedNames(getAllParamNames()); - nameDeduper.addUsedNames(getExistingLocalVariableNames(gfunc)); + nameDeduper.addUsedNames(getExistingLocalVariableNames()); Variable var = dvar.asLocalVariable(); String origName = var.getName(); @@ -317,11 +297,11 @@ public class DWARFFunction { var.setComment("Original name: " + origName); } - VariableUtilities.checkVariableConflict(gfunc, var, varStorage, true); - gfunc.addLocalVariable(var, SourceType.IMPORTED); + VariableUtilities.checkVariableConflict(function, var, varStorage, true); + function.addLocalVariable(var, SourceType.IMPORTED); } catch (InvalidInputException | DuplicateNameException e) { - appendComment(gfunc.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT, + appendComment(function.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT, "Local omitted variable %s[%s] scope starts here".formatted( dvar.getDeclInfoString(), varStorage != null ? varStorage.toString() : "UNKNOWN"), @@ -330,11 +310,19 @@ public class DWARFFunction { } } - @Override - public String toString() { - return String.format( - "DWARFFunction [\n\tdni=%s,\n\taddress=%s,\n\tparams=%s,\n\tsourceInfo=%s,\n\tlocalVarErrors=%s,\n\tretval=%s\n]", - name, address, params, sourceInfo, localVarErrors, retval); + private static boolean isBadSubprogramDef(DIEAggregate diea) { + if (diea.isDanglingDeclaration() || !diea.hasAttribute(DW_AT_low_pc)) { + return true; + } + + // fetch the low_pc attribute directly instead of calling diea.getLowPc() to avoid + // any fixups applied by lower level code + DWARFNumericAttribute attr = diea.getAttribute(DW_AT_low_pc, DWARFNumericAttribute.class); + if (attr != null && attr.getUnsignedValue() == 0) { + return true; + } + + return false; } private void appendComment(Address address, int commentType, String comment, String sep) { @@ -362,4 +350,152 @@ public class DWARFFunction { return null; } + public boolean syncWithExistingGhidraFunction(boolean createIfMissing) { + try { + Program currentProgram = getProgram().getGhidraProgram(); + function = currentProgram.getListing().getFunctionAt(address); + if (function != null) { + if (function.hasNoReturn() && !noReturn) { + // preserve the noReturn flag if set by earlier analyzer + noReturn = true; + } + } + + if (!createIfMissing && function == null) { + return false; + } + + // create a new symbol if one does not exist (symbol table will figure this out) + SymbolTable symbolTable = currentProgram.getSymbolTable(); + symbolTable.createLabel(address, name.getName(), namespace, SourceType.IMPORTED); + + // force new label to become primary (if already a function it will become function name) + SetLabelPrimaryCmd cmd = new SetLabelPrimaryCmd(address, name.getName(), namespace); + cmd.applyTo(currentProgram); + + if (isExternal) { + currentProgram.getSymbolTable().addExternalEntryPoint(address); + } + else { + currentProgram.getSymbolTable().removeExternalEntryPoint(address); + } + + function = currentProgram.getListing().getFunctionAt(address); + if (function == null) { + + // TODO: If not contained within program memory should they be considered external? + if (!currentProgram.getMemory() + .getLoadedAndInitializedAddressSet() + .contains(address)) { + Msg.warn(this, + "DWARF: unable to create function not contained within loaded memory: %s@%s" + .formatted(name, address)); + return false; + } + + // create 1-byte function if one does not exist - primary label will become function names + function = currentProgram.getFunctionManager() + .createFunction(null, address, new AddressSet(address), + SourceType.IMPORTED); + } + + return true; + } + catch (OverlappingFunctionException e) { + throw new AssertException(e); + } + catch (InvalidInputException e) { + Msg.error(this, "Failed to create function " + namespace + "/" + name.getName() + ": " + + e.getMessage()); + } + return false; + } + + public void runFixups() { + // Run all the DWARFFunctionFixup instances + for (DWARFFunctionFixup fixup : getProgram().getFunctionFixups()) { + try { + fixup.fixupDWARFFunction(this); + } + catch (DWARFException e) { + signatureCommitMode = CommitMode.SKIP; + } + if (signatureCommitMode == CommitMode.SKIP) { + break; + } + } + } + + public void updateFunctionSignature() { + try { + boolean includeStorageDetail = signatureCommitMode == CommitMode.STORAGE; + FunctionUpdateType functionUpdateType = includeStorageDetail + ? FunctionUpdateType.CUSTOM_STORAGE + : FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS; + + Parameter returnVar = retval.asReturnParameter(includeStorageDetail); + List parameters = getParameters(includeStorageDetail); + + if (includeStorageDetail && !retval.isZeroByte() && retval.isMissingStorage()) { + // TODO: this logic is faulty and borks the auto _return_storage_ptr_ when present + // Update return value in a separate step as its storage isn't typically specified + // in dwarf info. + // This will allow automagical storage assignment for return value by ghidra. + function.updateFunction(callingConventionName, returnVar, List.of(), + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); + returnVar = null; // don't update it in the second call to updateFunction() + } + + function.updateFunction(callingConventionName, returnVar, parameters, + functionUpdateType, true, SourceType.IMPORTED); + function.setVarArgs(varArg); + function.setNoReturn(noReturn); + } + catch (InvalidInputException | DuplicateNameException e) { + Msg.error(this, "Error updating function %s@%s with params: %s".formatted( + function.getName(), function.getEntryPoint().toString(), e.getMessage())); + Msg.error(this, "DIE info: " + diea.toString()); + } + } + + /** + * Returns a {@link FunctionDefinition} that reflects this function's information. + * + * @param includeCC boolean flag, if true the returned funcdef will include calling convention + * @return {@link FunctionDefinition} that reflects this function's information + */ + public FunctionDefinition asFunctionDefinition(boolean includeCC) { + ProgramBasedDataTypeManager dtm = getProgram().getGhidraProgram().getDataTypeManager(); + + FunctionDefinitionDataType funcDef = + new FunctionDefinitionDataType(name.getParentCP(), name.getName(), dtm); + funcDef.setReturnType(retval.type); + funcDef.setNoReturn(noReturn); + funcDef.setArguments(getParameterDefinitions()); + if (varArg) { + funcDef.setVarArgs(true); + } + if (getProgram().getImportOptions().isOutputSourceLocationInfo() && sourceInfo != null) { + funcDef.setComment(sourceInfo.getDescriptionStr()); + } + if (includeCC && callingConventionName != null) { + try { + funcDef.setCallingConvention(callingConventionName); + } + catch (InvalidInputException e) { + Msg.warn(this, "Unable to set calling convention name to %s for function def: %s" + .formatted(callingConventionName, funcDef)); + } + } + + return funcDef; + } + + @Override + public String toString() { + return String.format( + "DWARFFunction [name=%s, address=%s, sourceInfo=%s, retval=%s, params=%s, function=%s, diea=%s, signatureCommitMode=%s]", + name, address, sourceInfo, retval, params, function, diea, signatureCommitMode); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java index 8cf54478c0..0a23b09c94 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java @@ -25,7 +25,6 @@ import java.util.stream.Collectors; import ghidra.app.cmd.label.SetLabelPrimaryCmd; import ghidra.app.util.bin.format.dwarf4.*; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; -import ghidra.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode; import ghidra.program.database.function.OverlappingFunctionException; import ghidra.program.model.address.Address; @@ -33,7 +32,6 @@ import ghidra.program.model.address.AddressSet; import ghidra.program.model.data.*; import ghidra.program.model.data.DataUtilities.ClearDataMode; import ghidra.program.model.listing.*; -import ghidra.program.model.listing.Function.FunctionUpdateType; import ghidra.program.model.symbol.*; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.util.Msg; @@ -62,7 +60,6 @@ public class DWARFFunctionImporter { private Set processedOffsets = new HashSet<>(); private Set

functionsProcessed = new HashSet<>(); private Set
variablesProcesesed = new HashSet<>(); - private List functionFixups = DWARFFunctionFixup.findFixups(); private TaskMonitor monitor; @@ -142,6 +139,8 @@ public class DWARFFunctionImporter { } } logImportErrorSummary(); + + } private void logImportErrorSummary() { @@ -181,7 +180,7 @@ public class DWARFFunctionImporter { return; } - FunctionDefinition origFuncDef = dfunc.asFuncDef(); // before any fixups + FunctionDefinition origFuncDef = dfunc.asFunctionDefinition(true); // before any fixups if (functionsProcessed.contains(dfunc.address)) { markAllChildrenAsProcessed(dfunc.diea.getHeadFragment()); @@ -199,38 +198,23 @@ public class DWARFFunctionImporter { // location, we will get multiple side-effect output from processFuncChildren processFuncChildren(diea, dfunc, 0); - Function gfunc = createFunction(dfunc, diea); // create empty func with no info - if (gfunc == null) { + if (!dfunc.syncWithExistingGhidraFunction(true)) { + // if false, the stub ghidra function could not be found or created return; } - if (gfunc.hasNoReturn() && !dfunc.noReturn) { - // preserve the noReturn flag if set by earlier analyzer - dfunc.noReturn = true; - } + dfunc.runFixups(); - // Run all the DWARFFunctionFixup instances - for (DWARFFunctionFixup fixup : functionFixups) { - try { - fixup.fixupDWARFFunction(dfunc, gfunc); - } - catch (DWARFException e) { - dfunc.signatureCommitMode = CommitMode.SKIP; - break; - } - } - - decorateFunctionWithDWARFInfo(dfunc, gfunc, origFuncDef); + decorateFunctionWithDWARFInfo(dfunc, origFuncDef); if (dfunc.signatureCommitMode != CommitMode.SKIP) { - updateFunctionSignature(gfunc, dfunc); + dfunc.updateFunctionSignature(); } else { - Msg.error(this, - String.format( - "Failed to get DWARF function signature information, leaving undefined: %s@%s", - gfunc.getName(), gfunc.getEntryPoint())); - Msg.debug(this, "DIE info: " + diea.toString()); + Msg.warn(this, + "Failed to get DWARF function signature information, leaving undefined: %s@%s" + .formatted(dfunc.function.getName(), dfunc.function.getEntryPoint())); + //Msg.debug(this, "DIE info: " + diea.toString()); } for (DWARFVariable localVar : dfunc.localVars) { @@ -238,9 +222,23 @@ public class DWARFFunctionImporter { outputGlobal(localVar); // static variable scoped to the function } else { - dfunc.commitLocalVariable(localVar, gfunc); + dfunc.commitLocalVariable(localVar); } } + + if (importOptions.isCreateFuncSignatures()) { + DataType funcDefDT = dfunc.asFunctionDefinition(false); + funcDefDT = prog.getGhidraProgram() + .getDataTypeManager() + .addDataType(funcDefDT, DWARFDataTypeConflictHandler.INSTANCE); + + // Look for the source info in the funcdef die and fall back to its + // parent's source info (handles auto-generated ctors and such) + dwarfDTM.addDataType(diea.getOffset(), funcDefDT, + DWARFSourceInfo.getSourceInfoWithFallbackToParent(diea)); + + } + } private void decorateFunctionWithAlternateInfo(DWARFFunction dfunc, Function gfunc, @@ -257,12 +255,12 @@ public class DWARFFunctionImporter { } } - - private void decorateFunctionWithDWARFInfo(DWARFFunction dfunc, Function gfunc, + + private void decorateFunctionWithDWARFInfo(DWARFFunction dfunc, FunctionDefinition origFuncDef) { if (dfunc.sourceInfo != null) { // Move the function into the program tree of the file - moveIntoFragment(gfunc.getName(), dfunc.address, + moveIntoFragment(dfunc.function.getName(), dfunc.address, dfunc.highAddress != null ? dfunc.highAddress : dfunc.address.add(1), dfunc.sourceInfo.getFilename()); @@ -281,7 +279,7 @@ public class DWARFFunctionImporter { dfunc.name.getOriginalName()); } - FunctionDefinition newFuncDef = dfunc.asFuncDef(); + FunctionDefinition newFuncDef = dfunc.asFunctionDefinition(true); String origFuncDefStr = origFuncDef.getPrototypeString(true); if (!newFuncDef.getPrototypeString(true).equals(origFuncDefStr)) { // if the prototype of the function was modified during the fixup phase, append @@ -292,39 +290,6 @@ public class DWARFFunctionImporter { } - private void updateFunctionSignature(Function gfunc, DWARFFunction dfunc) { - try { - boolean includeStorageDetail = dfunc.signatureCommitMode == CommitMode.STORAGE; - FunctionUpdateType functionUpdateType = includeStorageDetail - ? FunctionUpdateType.CUSTOM_STORAGE - : FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS; - - Parameter returnVar = dfunc.retval.asReturnParameter(includeStorageDetail); - List parameters = dfunc.getParameters(includeStorageDetail); - - if (includeStorageDetail && !dfunc.retval.isZeroByte() && - dfunc.retval.isMissingStorage()) { - // Update return value in a separate step as its storage isn't typically specified - // in dwarf info. - // This will allow automagical storage assignment for return value by ghidra. - gfunc.updateFunction(dfunc.getCallingConventionName(), returnVar, List.of(), - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); - returnVar = null; // don't update it in the second call to updateFunction() - } - - gfunc.updateFunction(dfunc.getCallingConventionName(), returnVar, parameters, - functionUpdateType, true, SourceType.IMPORTED); - gfunc.setVarArgs(dfunc.varArg); - gfunc.setNoReturn(dfunc.noReturn); - } - catch (InvalidInputException | DuplicateNameException e) { - Msg.error(this, - String.format("Error updating function %s@%s with params: %s", - gfunc.getName(), gfunc.getEntryPoint().toString(), e.getMessage())); - Msg.error(this, "DIE info: " + dfunc.diea.toString()); - } - } - private void processFuncChildren(DIEAggregate diea, DWARFFunction dfunc, long offsetFromFuncStart) throws InvalidInputException, IOException, DWARFExpressionException { @@ -339,7 +304,11 @@ public class DWARFFunctionImporter { DWARFVariable localVar = DWARFVariable.readLocalVariable(childDIEA, dfunc, offsetFromFuncStart); if (localVar != null) { - dfunc.localVars.add(localVar); + if (prog.getImportOptions().isImportLocalVariables() || + localVar.isRamStorage()) { + // only retain the local var if option is turned on, or global/static variable + dfunc.localVars.add(localVar); + } } } break; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFImportOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFImportOptions.java index 17f621c4b7..2c2b86fa17 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFImportOptions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFImportOptions.java @@ -15,13 +15,94 @@ */ package ghidra.app.util.bin.format.dwarf4.next; +import ghidra.app.plugin.core.analysis.AnalysisOptionsUpdater; import ghidra.app.plugin.core.analysis.DWARFAnalyzer; +import ghidra.app.services.Analyzer; +import ghidra.framework.options.Options; /** * Import options exposed by the {@link DWARFAnalyzer} */ public class DWARFImportOptions { + private static final String OPTION_IMPORT_DATATYPES = "Import Data Types"; + private static final String OPTION_IMPORT_DATATYPES_DESC = + "Import data types defined in the DWARF debug info."; + + private static final String OPTION_PRELOAD_ALL_DIES = "Preload All DIEs"; + private static final String OPTION_PRELOAD_ALL_DIES_DESC = + "Preload all DIE records. Requires more memory, but necessary for some non-standard " + + "layouts."; + + private static final String OPTION_IMPORT_FUNCS = "Import Functions"; + private static final String OPTION_IMPORT_FUNCS_DESC = + "Import function information defined in the DWARF debug info\n" + + "(implies 'Import Data Types' is selected)."; + + private static final String OPTION_IMPORT_LIMIT_DIE_COUNT = "Debug Item Limit"; + private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_DESC = + "If the number of DWARF debug items are greater than this setting, DWARF analysis will " + + "be skipped."; + + private static final String OPTION_OUTPUT_SOURCE_INFO = "Output Source Info"; + private static final String OPTION_OUTPUT_SOURCE_INFO_DESC = + "Include source code location info (filename:linenumber) in comments attached to the " + + "Ghidra datatype or function or variable created."; + + private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE Info"; + private static final String OPTION_OUTPUT_DWARF_DIE_INFO_DESC = + "Include DWARF DIE offset info in comments attached to the Ghidra datatype or function " + + "or variable created."; + + private static final String OPTION_NAME_LENGTH_CUTOFF = "Maximum Name Length"; + private static final String OPTION_NAME_LENGTH_CUTOFF_DESC = + "Truncate symbol and type names longer than this limit. Range 20..2000"; + + private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS = "Add Lexical Block Comments"; + private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC = + "Add comments to the start of lexical blocks"; + + private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS = + "Add Inlined Functions Comments"; + private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC = + "Add comments to the start of inlined functions"; + + private static final String OPTION_OUTPUT_FUNC_SIGS = "Create Function Signatures"; + private static final String OPTION_OUTPUT_FUNC_SIGS_DESC = + "Create function signature data types for each function encountered in the DWARF debug " + + "data."; + + private static final String OPTION_TRY_PACK_STRUCTS = "Try To Pack Structs"; + private static final String OPTION_TRY_PACK_STRUCTS_DESC = + "Try to pack structure/union data types."; + + private static final String OPTION_IMPORT_LOCAL_VARS = "Import Local Variable Info"; + private static final String OPTION_IMPORT_LOCAL_VARS_DESC = + "Import local variable information from DWARF and attempt to create Ghidra local variables."; + + //================================================================================================== + // Old Option Names - Should stick around for multiple major versions after 10.2 + //================================================================================================== + + private static final String OPTION_IMPORT_DATATYPES_OLD = "Import data types"; + private static final String OPTION_PRELOAD_ALL_DIES_OLD = "Preload all DIEs"; + private static final String OPTION_IMPORT_FUNCS_OLD = "Import functions"; + private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_OLD = "Debug item count limit"; + private static final String OPTION_OUTPUT_SOURCE_INFO_OLD = "Output Source info"; + private static final String OPTION_OUTPUT_DWARF_DIE_INFO_OLD = "Output DWARF DIE info"; + private static final String OPTION_NAME_LENGTH_CUTOFF_OLD = "Name length cutoff"; + private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD = "Lexical block comments"; + private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD = + "Inlined functions comments"; + private static final String OPTION_OUTPUT_FUNC_SIGS_OLD = "Output function signatures"; + + //================================================================================================== + // End Old Option Names + //================================================================================================== + private static final int DEFAULT_IMPORT_LIMIT_DIE_COUNT = 2_000_000; + + private AnalysisOptionsUpdater optionsUpdater = new AnalysisOptionsUpdater(); + private boolean outputDWARFLocationInfo = false; private boolean outputDWARFDIEInfo = false; private boolean elideTypedefsWithSameName = true; @@ -37,9 +118,37 @@ public class DWARFImportOptions { private boolean organizeTypesBySourceFile = true; private boolean tryPackStructs = true; private boolean specialCaseSizedBaseTypes = true; + private boolean importLocalVariables = true; + /** + * Create new instance + */ public DWARFImportOptions() { - // nada + optionsUpdater.registerReplacement(OPTION_IMPORT_DATATYPES, OPTION_IMPORT_DATATYPES_OLD); + optionsUpdater.registerReplacement(OPTION_PRELOAD_ALL_DIES, OPTION_PRELOAD_ALL_DIES_OLD); + optionsUpdater.registerReplacement(OPTION_IMPORT_FUNCS, OPTION_IMPORT_FUNCS_OLD); + optionsUpdater.registerReplacement(OPTION_IMPORT_LIMIT_DIE_COUNT, + OPTION_IMPORT_LIMIT_DIE_COUNT_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_SOURCE_INFO, + OPTION_OUTPUT_SOURCE_INFO_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_DWARF_DIE_INFO, + OPTION_OUTPUT_DWARF_DIE_INFO_OLD); + optionsUpdater.registerReplacement(OPTION_NAME_LENGTH_CUTOFF, + OPTION_NAME_LENGTH_CUTOFF_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, + OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_INLINE_FUNC_COMMENTS, + OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_FUNC_SIGS, OPTION_OUTPUT_FUNC_SIGS_OLD); + } + + /** + * See {@link Analyzer#getOptionsUpdater()} + * + * @return {@link AnalysisOptionsUpdater} + */ + public AnalysisOptionsUpdater getOptionsUpdater() { + return optionsUpdater; } /** @@ -236,7 +345,7 @@ public class DWARFImportOptions { /** * Option to control a feature that copies anonymous types into a structure's "namespace" - * CategoryPath and giving that anonymous type a new name based on the structure's field's + * CategoryPath and giving that anonymousfunction.getEntryPoint() type a new name based on the structure's field's * name. * * @param b boolean flag to set. @@ -322,4 +431,82 @@ public class DWARFImportOptions { public void setSpecialCaseSizedBaseTypes(boolean b) { this.specialCaseSizedBaseTypes = b; } + + public boolean isImportLocalVariables() { + return importLocalVariables; + } + + public void setImportLocalVariables(boolean importLocalVariables) { + this.importLocalVariables = importLocalVariables; + } + + /** + * See {@link Analyzer#registerOptions(Options, ghidra.program.model.listing.Program)} + * + * @param options {@link Options} + */ + public void registerOptions(Options options) { + options.registerOption(OPTION_IMPORT_DATATYPES, isImportDataTypes(), null, + OPTION_IMPORT_DATATYPES_DESC); + + options.registerOption(OPTION_PRELOAD_ALL_DIES, isPreloadAllDIEs(), null, + OPTION_PRELOAD_ALL_DIES_DESC); + + options.registerOption(OPTION_IMPORT_FUNCS, isImportFuncs(), null, + OPTION_IMPORT_FUNCS_DESC); + + options.registerOption(OPTION_OUTPUT_DWARF_DIE_INFO, isOutputDIEInfo(), null, + OPTION_OUTPUT_DWARF_DIE_INFO_DESC); + + options.registerOption(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, isOutputLexicalBlockComments(), + null, OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC); + + options.registerOption(OPTION_OUTPUT_INLINE_FUNC_COMMENTS, isOutputInlineFuncComments(), + null, OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC); + + options.registerOption(OPTION_OUTPUT_SOURCE_INFO, isOutputSourceLocationInfo(), null, + OPTION_OUTPUT_SOURCE_INFO_DESC); + + options.registerOption(OPTION_IMPORT_LIMIT_DIE_COUNT, getImportLimitDIECount(), null, + OPTION_IMPORT_LIMIT_DIE_COUNT_DESC); + + options.registerOption(OPTION_NAME_LENGTH_CUTOFF, getNameLengthCutoff(), null, + OPTION_NAME_LENGTH_CUTOFF_DESC); + + options.registerOption(OPTION_OUTPUT_FUNC_SIGS, isCreateFuncSignatures(), null, + OPTION_OUTPUT_FUNC_SIGS_DESC); + + options.registerOption(OPTION_TRY_PACK_STRUCTS, isTryPackStructs(), null, + OPTION_TRY_PACK_STRUCTS_DESC); + + options.registerOption(OPTION_IMPORT_LOCAL_VARS, isImportLocalVariables(), null, + OPTION_IMPORT_LOCAL_VARS_DESC); + } + + /** + * See {@link Analyzer#optionsChanged(Options, ghidra.program.model.listing.Program)} + * + * @param options {@link Options} + */ + public void optionsChanged(Options options) { + setOutputDIEInfo(options.getBoolean(OPTION_OUTPUT_DWARF_DIE_INFO, isOutputDIEInfo())); + setPreloadAllDIEs(options.getBoolean(OPTION_PRELOAD_ALL_DIES, isPreloadAllDIEs())); + setOutputSourceLocationInfo( + options.getBoolean(OPTION_OUTPUT_SOURCE_INFO, isOutputSourceLocationInfo())); + setOutputLexicalBlockComments(options.getBoolean(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, + isOutputLexicalBlockComments())); + setOutputInlineFuncComments( + options.getBoolean(OPTION_OUTPUT_INLINE_FUNC_COMMENTS, isOutputInlineFuncComments())); + setImportDataTypes(options.getBoolean(OPTION_IMPORT_DATATYPES, isImportDataTypes())); + setImportFuncs(options.getBoolean(OPTION_IMPORT_FUNCS, isImportFuncs())); + setImportLimitDIECount( + options.getInt(OPTION_IMPORT_LIMIT_DIE_COUNT, getImportLimitDIECount())); + setNameLengthCutoff(options.getInt(OPTION_NAME_LENGTH_CUTOFF, getNameLengthCutoff())); + setCreateFuncSignatures( + options.getBoolean(OPTION_OUTPUT_FUNC_SIGS, isCreateFuncSignatures())); + setTryPackDataTypes(options.getBoolean(OPTION_TRY_PACK_STRUCTS, isTryPackStructs())); + setImportLocalVariables( + options.getBoolean(OPTION_IMPORT_LOCAL_VARS, isImportLocalVariables())); + + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFParser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFParser.java index 034ba9ad67..14cf1d2972 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFParser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFParser.java @@ -15,11 +15,10 @@ */ package ghidra.app.util.bin.format.dwarf4.next; +import java.io.IOException; import java.util.Collections; import java.util.List; -import java.io.IOException; - import ghidra.app.plugin.core.datamgr.util.DataTypeUtils; import ghidra.app.util.bin.format.dwarf4.DWARFException; import ghidra.program.model.data.*; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java index 8d30175f09..9905f1a9c9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java @@ -15,10 +15,9 @@ */ package ghidra.app.util.bin.format.dwarf4.next; -import java.util.*; - import java.io.Closeable; import java.io.IOException; +import java.util.*; import org.apache.commons.collections4.ListValuedMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; @@ -29,8 +28,11 @@ import ghidra.app.util.bin.format.dwarf4.attribs.DWARFAttributeFactory; import ghidra.app.util.bin.format.dwarf4.encoding.*; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; import ghidra.app.util.bin.format.dwarf4.external.ExternalDebugInfo; +import ghidra.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup; import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.*; +import ghidra.app.util.bin.format.golang.rtti.GoSymbolName; import ghidra.app.util.opinion.*; +import ghidra.formats.gfilesystem.FSUtilities; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.CategoryPath; @@ -64,11 +66,12 @@ public class DWARFProgram implements Closeable { * all the work that querying all registered DWARFSectionProviders would take. *

* If the program is an Elf binary, it must have (at least) ".debug_info" and ".debug_abbr", - * program sections, or their compressed "z" versions. + * program sections, or their compressed "z" versions, or ExternalDebugInfo that would point + * to an external DWARF file. *

- * If the program is a MachO binary (ie. Mac), it must have a ".dSYM" directory co-located next to the - * original binary file on the native filesystem. (lie. outside of Ghidra). See the DSymSectionProvider - * for more info. + * If the program is a MachO binary (Mac), it must have a ".dSYM" directory co-located + * next to the original binary file on the native filesystem (outside of Ghidra). See the + * DSymSectionProvider for more info. *

* @param program {@link Program} to test * @return boolean true if program probably has DWARF info, false if not @@ -182,7 +185,7 @@ public class DWARFProgram implements Closeable { private final boolean stackGrowsNegative; - private final Map opaqueProps = new HashMap<>(); + private List functionFixups; /** * Main constructor for DWARFProgram. @@ -272,6 +275,16 @@ public class DWARFProgram implements Closeable { debugStrings.clear(); dniCache.clear(); clearDIEIndexes(); + + if (functionFixups != null) { + for (DWARFFunctionFixup funcFixup : functionFixups) { + if (funcFixup instanceof Closeable c) { + FSUtilities.uncheckedClose(c, null); + } + } + functionFixups.clear(); + functionFixups = null; + } } public DWARFImportOptions getImportOptions() { @@ -464,7 +477,7 @@ public class DWARFProgram implements Closeable { String origName = isAnon ? null : name; String workingName = ensureSafeNameLength(name); - workingName = fixupSpecialMeaningCharacters(workingName); + workingName = GoSymbolName.fixGolangSpecialSymbolnameChars(workingName); DWARFNameInfo result = parentDNI.createChild(origName, workingName, DWARFUtil.getSymbolTypeFromDIE(diea)); @@ -570,16 +583,6 @@ public class DWARFProgram implements Closeable { return strs; } - private String fixupSpecialMeaningCharacters(String s) { - // golang specific hacks: - // "\u00B7" -> "." - // "\u2215" -> "/" - if (s.contains("\u00B7") || s.contains("\u2215")) { - s = s.replaceAll("\u00B7", ".").replaceAll("\u2215", "/"); - } - return s; - } - public DWARFNameInfo getName(DIEAggregate diea) { DWARFNameInfo dni = lookupDNIByOffset(diea.getOffset()); if (dni == null) { @@ -1064,12 +1067,10 @@ public class DWARFProgram implements Closeable { return stackGrowsNegative; } - public T getOpaqueProperty(Object key, T defaultValue, Class valueClass) { - Object obj = opaqueProps.get(key); - return obj != null && valueClass.isInstance(obj) ? valueClass.cast(obj) : defaultValue; - } - - public void setOpaqueProperty(Object key, Object value) { - opaqueProps.put(key, value); + public List getFunctionFixups() { + if (functionFixups == null) { + functionFixups = DWARFFunctionFixup.findFixups(); + } + return functionFixups; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFVariable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFVariable.java index 13371400e7..283a99d83f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFVariable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFVariable.java @@ -38,8 +38,9 @@ import ghidra.util.exception.InvalidInputException; */ public class DWARFVariable { /** - * Creates an unnamed, storage-less {@link DWARFVariable} from a DataType. - * + * Creates an unnamed, storage-less {@link DWARFVariable} from a DataType. + * + * @param dfunc containing function * @param dt {@link DataType} of the variable * @return new {@link DWARFVariable}, never null */ @@ -52,9 +53,8 @@ public class DWARFVariable { * * @param diea {@link DIEAggregate} DW_TAG_formal_parameter * @param dfunc {@link DWARFFunction} that this parameter is attached to - * @param paramOrdinal + * @param paramOrdinal ordinal in containing list * @return new parameter, never null, possibly without storage info - * @throws IOException if error */ public static DWARFVariable readParameter(DIEAggregate diea, DWARFFunction dfunc, int paramOrdinal) { @@ -72,6 +72,7 @@ public class DWARFVariable { * * @param diea {@link DIEAggregate} DW_TAG_variable * @param dfunc {@link DWARFFunction} that this local var belongs to + * @param offsetFromFuncStart offset from start of containing function * @return new DWARFVariable that represents a local var, or null if * error reading storage info */ @@ -199,13 +200,6 @@ public class DWARFVariable { return stackStorage.getOffset(); } - public String getToolTip() { - return """ - Built In Data Types
-   %s - """.formatted("DEFAULT_DATA_ORG_DESCRIPTION"); - } - /** * @return true if this variable's storage is in ram */ @@ -482,8 +476,7 @@ public class DWARFVariable { return result; } - public Parameter asParameter(boolean includeStorageDetail, Program program) - throws InvalidInputException { + public Parameter asParameter(boolean includeStorageDetail) throws InvalidInputException { VariableStorage paramStorage = !isMissingStorage() && includeStorageDetail ? getVariableStorage() : VariableStorage.UNASSIGNED_STORAGE; @@ -493,7 +486,7 @@ public class DWARFVariable { ParameterImpl result = new ParameterImpl(newName, Parameter.UNASSIGNED_ORDINAL, type, paramStorage, true, - program, SourceType.IMPORTED); + program.getGhidraProgram(), SourceType.IMPORTED); result.setComment(getParamComment()); return result; @@ -513,24 +506,14 @@ public class DWARFVariable { public Parameter asReturnParameter(boolean includeStorageDetail) throws InvalidInputException { - VariableStorage storage = isVoidType() + VariableStorage varStorage = isVoidType() ? VariableStorage.VOID_STORAGE : !isMissingStorage() && includeStorageDetail ? getVariableStorage() : VariableStorage.UNASSIGNED_STORAGE; - return new ReturnParameterImpl(type, storage, true, program.getGhidraProgram()); + return new ReturnParameterImpl(type, varStorage, true, program.getGhidraProgram()); } - public void appendComment(String prefix, String comment, String sep) { - if (comment == null || comment.isEmpty()) { - comment = ""; - } - else { - comment += sep; - } - this.comment += prefix + comment; - } - public String getDeclInfoString() { return "%s:%s".formatted(name.getName(), type.getDisplayName()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java index 890eda7393..6a4cfac76e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java @@ -15,16 +15,14 @@ */ package ghidra.app.util.bin.format.golang; -import static ghidra.app.util.bin.StructConverter.ASCII; -import static ghidra.app.util.bin.StructConverter.BYTE; +import static ghidra.app.util.bin.StructConverter.*; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.*; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.MemoryByteProvider; +import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.elf.info.ElfInfoItem; import ghidra.framework.options.Options; import ghidra.program.model.address.Address; @@ -33,8 +31,7 @@ import ghidra.program.model.data.DataUtilities.ClearDataMode; import ghidra.program.model.lang.Endian; import ghidra.program.model.listing.Program; import ghidra.program.model.util.CodeUnitInsertionException; -import ghidra.util.Msg; -import ghidra.util.NumericUtilities; +import ghidra.util.*; /** * A program section that contains Go build information strings, namely go module package names, @@ -109,7 +106,14 @@ public class GoBuildInfo implements ElfInfoItem { throw new IOException("Mixed endian-ness"); } - return readStringInfo(reader, inlineStr, program, pointerSize); + DataTypeManager dtm = program.getDataTypeManager(); + StructureDataType struct = + new StructureDataType(GoConstants.GOLANG_CATEGORYPATH, "GoBuildInfo", 0, dtm); + struct.add(new ArrayDataType(ASCII, 14, -1, dtm), "magic", "\\xff Go buildinf:"); + struct.add(BYTE, "ptrSize", null); + struct.add(BYTE, "flags", null); + + return readStringInfo(reader, inlineStr, program, pointerSize, struct); } /** @@ -139,10 +143,11 @@ public class GoBuildInfo implements ElfInfoItem { private final GoModuleInfo moduleInfo; // info about the module that contains the main package. version typically will be "(devel)" private final List dependencies; private final List buildSettings; // compile/linker flags used during build process + private final StructureDataType struct; private GoBuildInfo(int pointerSize, Endian endian, String version, String path, GoModuleInfo moduleInfo, List dependencies, - List buildSettings) { + List buildSettings, StructureDataType struct) { this.pointerSize = pointerSize; this.endian = endian; this.version = version; @@ -150,6 +155,7 @@ public class GoBuildInfo implements ElfInfoItem { this.moduleInfo = moduleInfo; this.dependencies = dependencies; this.buildSettings = buildSettings; + this.struct = struct; } public int getPointerSize() { @@ -189,7 +195,6 @@ public class GoBuildInfo implements ElfInfoItem { decorateProgramInfo(program.getOptions(Program.PROGRAM_INFO)); try { - StructureDataType struct = toStructure(program.getDataTypeManager()); if (struct != null) { DataUtilities.createData(program, address, struct, -1, false, ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA); @@ -204,8 +209,7 @@ public class GoBuildInfo implements ElfInfoItem { GoVer.setProgramPropertiesWithOriginalVersionString(props, getVersion()); props.setString("Golang app path", getPath()); if (getModuleInfo() != null) { - getModuleInfo() - .asKeyValuePairs("Golang main package ") + getModuleInfo().asKeyValuePairs("Golang main package ") .entrySet() .stream() .forEach(entry -> props.setString(entry.getKey(), entry.getValue())); @@ -222,13 +226,7 @@ public class GoBuildInfo implements ElfInfoItem { } StructureDataType toStructure(DataTypeManager dtm) { - StructureDataType result = - new StructureDataType(GoConstants.GOLANG_CATEGORYPATH, "GoBuildInfo", 0, dtm); - result.add(new ArrayDataType(ASCII, 14, -1, dtm), "magic", "\\xff Go buildinf:"); - result.add(BYTE, "ptrSize", null); - result.add(BYTE, "flags", null); - - return result; + return struct.clone(dtm); } @Override @@ -240,18 +238,37 @@ public class GoBuildInfo implements ElfInfoItem { //--------------------------------------------------------------------------------------------- private static GoBuildInfo readStringInfo(BinaryReader reader, boolean inlineStr, - Program program, int ptrSize) throws IOException { + Program program, int ptrSize, StructureDataType struct) throws IOException { + DataTypeManager dtm = program.getDataTypeManager(); String moduleString; String versionString; if (inlineStr) { reader.setPointerIndex(32 /* static start of inline strings */); - versionString = reader.readNext(GoBuildInfo::varlenString); - byte[] moduleStringBytes = reader.readNext(GoBuildInfo::varlenBytes); + LEB128Info verStrLen = reader.readNext(LEB128Info::unsigned); + byte[] versionStringBytes = reader.readNextByteArray(verStrLen.asInt32()); + versionString = new String(versionStringBytes, StandardCharsets.UTF_8); - moduleString = extractModuleString(moduleStringBytes); + LEB128Info modStrLen = reader.readNext(LEB128Info::unsigned); + byte[] moduleStringBytes = reader.readNextByteArray(modStrLen.asInt32()); + + struct.add(new ArrayDataType(BYTE, 16, -1, dtm), -1, "padding", null); + struct.add(new UnsignedLeb128DataType(dtm), verStrLen.getLength(), "versionlen", null); + struct.add(new ArrayDataType(ASCII, verStrLen.asInt32(), -1, dtm), -1, "version", null); + struct.add(new UnsignedLeb128DataType(dtm), modStrLen.getLength(), "modulelen", null); + + moduleString = extractModuleString(moduleStringBytes, struct); + + try { + String structNameSuffix = "_inline_%d_%d_%d_%d".formatted(verStrLen.getLength(), + verStrLen.asInt32(), modStrLen.getLength(), modStrLen.asInt32()); + struct.setName(struct.getName() + structNameSuffix); + } + catch (InvalidNameException e) { + // ignore + } } else { reader.setPointerIndex(16 /* static start of 2 string pointers */); @@ -267,15 +284,19 @@ public class GoBuildInfo implements ElfInfoItem { fullReader.setPointerIndex(moduleStrOffset); byte[] moduleStrBytes = readRawGoString(fullReader, ptrSize); - moduleString = extractModuleString(moduleStrBytes); + moduleString = extractModuleString(moduleStrBytes, null); + + DataType ofsDT = AbstractIntegerDataType.getUnsignedDataType(ptrSize, dtm); + struct.add(ofsDT, -1, "versionofs", null); + struct.add(ofsDT, -1, "moduleofs", null); } return parseBuildInfo(ptrSize, reader.isBigEndian() ? Endian.BIG : Endian.LITTLE, - versionString, moduleString); + versionString, moduleString, struct); } private static GoBuildInfo parseBuildInfo(int pointerSize, Endian endian, String versionString, - String moduleString) throws IOException { + String moduleString, StructureDataType struct) throws IOException { String path = null; GoModuleInfo module = null; List deps = new ArrayList<>(); @@ -303,9 +324,8 @@ public class GoBuildInfo implements ElfInfoItem { path = lineParts[1]; break; case "mod": { - GoModuleInfo replace = replaceInfo != null - ? GoModuleInfo.fromString(replaceInfo, null) - : null; + GoModuleInfo replace = + replaceInfo != null ? GoModuleInfo.fromString(replaceInfo, null) : null; module = GoModuleInfo.fromString(lineParts[1], replace); break; } @@ -326,10 +346,11 @@ public class GoBuildInfo implements ElfInfoItem { } return new GoBuildInfo(pointerSize, endian, versionString, path, module, deps, - buildSettings); + buildSettings, struct); } - private static String extractModuleString(byte[] bytes) throws IOException { + private static String extractModuleString(byte[] bytes, StructureDataType struct) + throws IOException { int sentLen = INFOSTART_SENTINEL.length; // both are same len if (bytes.length < sentLen * 2) { return ""; @@ -340,36 +361,19 @@ public class GoBuildInfo implements ElfInfoItem { !Arrays.equals(INFOEND_SENTINEL, 0, sentLen, bytes, sentEndStart, bytes.length)) { throw new IOException("bad sentinel"); } - return new String(bytes, sentLen, bytes.length - (sentLen * 2), StandardCharsets.UTF_8); - } + int moduleStrLen = bytes.length - (sentLen * 2); + if (struct != null) { + struct.add(new ArrayDataType(BYTE, sentLen, -1, struct.getDataTypeManager()), -1, + "sentinelstart", null); + struct.add(new ArrayDataType(ASCII, moduleStrLen, -1, struct.getDataTypeManager()), -1, + "moduleinfo", null); + struct.add(new ArrayDataType(BYTE, sentLen, -1, struct.getDataTypeManager()), -1, + "sentinelend", null); + } - /** - * Reads a pascal-ish string used by go in lower level file format. - *

- * Not to be confused with a real golang string produced when strings are created in - * a golang program. - * - * @param reader BinaryReader - * @return string at the current index of the BinaryReader - * @throws IOException if error - */ - private static String varlenString(BinaryReader reader) throws IOException { - byte[] bytes = varlenBytes(reader); - return new String(bytes, StandardCharsets.UTF_8); - } + return new String(bytes, sentLen, moduleStrLen, StandardCharsets.UTF_8); - /** - * Reads a variable length byte array. - * - * @param reader BinaryReader - * @return variable length byte array at the current index of the BinaryReader - * @throws IOException if error - */ - private static byte[] varlenBytes(BinaryReader reader) throws IOException { - int strLen = reader.readNextUnsignedVarIntExact(LEB128::unsigned); - byte[] bytes = reader.readNextByteArray(strLen); - return bytes; } private static String readGoString(BinaryReader reader, int ptrSize) throws IOException { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoConstants.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoConstants.java index 8b4cbd2eb0..e04b5b5b6a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoConstants.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoConstants.java @@ -27,6 +27,11 @@ public class GoConstants { * Category path to place golang types in */ public static final CategoryPath GOLANG_CATEGORYPATH = new CategoryPath("/golang"); + public static final CategoryPath GOLANG_BOOTSTRAP_FUNCS_CATEGORYPATH = + GOLANG_CATEGORYPATH.extend("functions"); + public static final CategoryPath GOLANG_RECOVERED_TYPES_CATEGORYPATH = + new CategoryPath("/golang-recovered"); + public static final String GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME = "abi-internal"; public static final String GOLANG_ABI0_CALLINGCONVENTION_NAME = "abi0"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionFixup.java index 86696eaaa5..00a8f4a212 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionFixup.java @@ -18,8 +18,8 @@ package ghidra.app.util.bin.format.golang; import java.util.ArrayList; import java.util.List; -import ghidra.app.plugin.core.analysis.GolangSymbolAnalyzer; import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.program.model.lang.Register; @@ -191,14 +191,12 @@ public class GoFunctionFixup { long stackOffset = storageAllocator.getStackAllocation(dt); return new ParameterImpl(oldParam.getName(), dt, (int) stackOffset, program); } - else { - if (DWARFUtil.isEmptyArray(dt)) { - dt = makeEmptyArrayDataType(dt); - } - Address zerobaseAddress = GolangSymbolAnalyzer.getZerobaseAddress(program); - return new ParameterImpl(oldParam.getName(), dt, zerobaseAddress, program, - SourceType.USER_DEFINED); + if (DWARFUtil.isEmptyArray(dt)) { + dt = makeEmptyArrayDataType(dt); } + Address zerobaseAddress = GoRttiMapper.getZerobaseAddress(program); + return new ParameterImpl(oldParam.getName(), dt, zerobaseAddress, program, + SourceType.USER_DEFINED); } @@ -214,10 +212,6 @@ public class GoFunctionFixup { if (returnDT == null || Undefined.isUndefined(returnDT) || DWARFUtil.isVoid(returnDT)) { return null; } - -// status refactoring return result storage calc to use new GoFunctionMultiReturn -// class to embed ordinal order in data type so that original data type and calc info -// can be recreated. GoFunctionMultiReturn multiReturn; if ((multiReturn = @@ -246,7 +240,7 @@ public class GoFunctionFixup { if (DWARFUtil.isEmptyArray(returnDT)) { returnDT = makeEmptyArrayDataType(returnDT); } - varnodes.add(new Varnode(GolangSymbolAnalyzer.getZerobaseAddress(program), 1)); + varnodes.add(new Varnode(GoRttiMapper.getZerobaseAddress(program), 1)); } else { allocateReturnStorage(program, "return-value-alias-variable", returnDT, @@ -273,9 +267,20 @@ public class GoFunctionFixup { else { if (!DWARFUtil.isZeroByteDataType(dt)) { long stackOffset = storageAllocator.getStackAllocation(dt); - varnodes.add( - new Varnode(program.getAddressFactory().getStackSpace().getAddress(stackOffset), + Varnode prev = !varnodes.isEmpty() ? varnodes.get(varnodes.size() - 1) : null; + if (prev != null && prev.getAddress().isStackAddress()) { +// if ( prev.getAddress().getOffset() + prev.getSize() != stackOffset ) { +// throw new InvalidInputException("Non-adjacent stack storage"); +// } + Varnode updatedVN = + new Varnode(prev.getAddress(), prev.getSize() + dt.getLength()); + varnodes.set(varnodes.size() - 1, updatedVN); + } + else { + varnodes.add(new Varnode( + program.getAddressFactory().getStackSpace().getAddress(stackOffset), dt.getLength())); + } // when the return value is on the stack, the decompiler's output is improved // when the function has something at the stack location @@ -289,7 +294,8 @@ public class GoFunctionFixup { public static boolean isGolangAbi0Func(Function func) { Address funcAddr = func.getEntryPoint(); for (Symbol symbol : func.getProgram().getSymbolTable().getSymbolsAsIterator(funcAddr)) { - if (symbol.getSymbolType() == SymbolType.LABEL) { + if (symbol.getSymbolType() == SymbolType.LABEL || + symbol.getSymbolType() == SymbolType.FUNCTION) { String labelName = symbol.getName(); if (labelName.endsWith("abi0")) { return true; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoParamStorageAllocator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoParamStorageAllocator.java index f1407d1105..74c1169b27 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoParamStorageAllocator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoParamStorageAllocator.java @@ -19,7 +19,8 @@ import java.util.*; import ghidra.app.util.bin.format.dwarf4.DWARFUtil; import ghidra.program.model.data.*; -import ghidra.program.model.lang.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.listing.Program; import ghidra.util.NumericUtilities; @@ -37,8 +38,6 @@ public class GoParamStorageAllocator { private GoRegisterInfo callspecInfo; private long stackOffset; private boolean isBigEndian; - private PrototypeModel abiInternalCallingConvention; - private PrototypeModel abi0CallingConvention; private String archDescription; /** @@ -57,9 +56,6 @@ public class GoParamStorageAllocator { this.stackOffset = callspecInfo.getStackInitialOffset(); this.regs = List.of(callspecInfo.getIntRegisters(), callspecInfo.getFloatRegisters()); this.isBigEndian = lang.isBigEndian(); - this.abiInternalCallingConvention = - program.getFunctionManager().getCallingConvention("abi-internal"); - this.abi0CallingConvention = program.getFunctionManager().getCallingConvention("abi0"); this.archDescription = "%s_%d".formatted(lang.getLanguageDescription().getProcessor().toString(), lang.getLanguageDescription().getSize()); @@ -67,30 +63,19 @@ public class GoParamStorageAllocator { private GoParamStorageAllocator(List> regs, int[] nextReg, GoRegisterInfo callspecInfo, long stackOffset, boolean isBigEndian, - PrototypeModel abiInternalCallingConvention, PrototypeModel abi0CallingConvention, String archDescription) { this.regs = List.of(regs.get(INTREG), regs.get(FLOATREG)); this.nextReg = new int[] { nextReg[INTREG], nextReg[FLOATREG] }; this.callspecInfo = callspecInfo; this.stackOffset = stackOffset; this.isBigEndian = isBigEndian; - this.abiInternalCallingConvention = abiInternalCallingConvention; - this.abi0CallingConvention = abi0CallingConvention; this.archDescription = archDescription; } @Override public GoParamStorageAllocator clone() { return new GoParamStorageAllocator(regs, nextReg, callspecInfo, stackOffset, isBigEndian, - abiInternalCallingConvention, abi0CallingConvention, archDescription); - } - - public PrototypeModel getAbi0CallingConvention() { - return abi0CallingConvention; - } - - public PrototypeModel getAbiInternalCallingConvention() { - return abiInternalCallingConvention; + archDescription); } public String getArchDescription() { @@ -146,6 +131,24 @@ public class GoParamStorageAllocator { return regs.get(INTREG).isEmpty() && regs.get(FLOATREG).isEmpty(); } + /** + * Returns the integer parameter that follows the supplied register. + * + * @param reg register in the integer reg list + * @return the following register of the queried register, or null if no following register + * found + */ + public Register getNextIntParamRegister(Register reg) { + List intRegs = regs.get(INTREG); + for (int regNum = 0; regNum < intRegs.size() - 1; regNum++) { + Register tmpReg = intRegs.get(regNum); + if (tmpReg.equals(reg)) { + return intRegs.get(regNum + 1); + } + } + return null; + } + /** * Returns a list of {@link Register registers} that will successfully store the specified * data type, as well as marking those registers as used and unavailable. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java index 9ac8d69957..1ffc556ea3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java @@ -15,43 +15,47 @@ */ package ghidra.app.util.bin.format.golang; -import java.util.ArrayList; -import java.util.List; +import java.util.*; -import ghidra.app.plugin.core.analysis.GolangSymbolAnalyzer; -import ghidra.app.util.bin.format.dwarf4.DIEAggregate; -import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.app.plugin.core.analysis.TransientProgramProperties; +import ghidra.app.plugin.core.analysis.TransientProgramProperties.SCOPE; +import ghidra.app.util.bin.format.dwarf4.*; import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage; import ghidra.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup; import ghidra.app.util.bin.format.dwarf4.next.*; import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode; +import ghidra.app.util.bin.format.golang.rtti.GoFuncData; +import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.program.model.lang.Register; -import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.*; import ghidra.program.model.pcode.Varnode; import ghidra.program.model.symbol.SymbolType; import ghidra.util.Msg; import ghidra.util.classfinder.ExtensionPointProperties; -import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; /** - * Fixups for golang functions. + * Fixups for golang functions found during DWARF processing. *

* Fixes storage of parameters to match the go callspec and modifies parameter lists to match * Ghidra's capabilities. *

- * Special characters used by golang in symbol names are fixed up in - * DWARFProgram.fixupSpecialMeaningCharacters(): - *

  • "\u00B7" (middle dot) -> "." - *
  • "\u2215" (weird slash) -> "/" + * Special characters used by golang in symbol names (middle dot \u00B7, weird slash \u2215) are + * fixed up in DWARFProgram.getDWARFNameInfo() by calling + * GoSymbolName.fixGolangSpecialSymbolnameChars(). + *

    + * Go's 'unique' usage of DW_TAG_subroutine_type to define its ptr-to-ptr-to-func is handled in + * DWARFDataTypeImporter.makeDataTypeForFunctionDefinition(). + *

    */ @ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_EARLY) public class GolangDWARFFunctionFixup implements DWARFFunctionFixup { public static final CategoryPath GOLANG_API_EXPORT = new CategoryPath(CategoryPath.ROOT, "GolangAPIExport"); + private static final String GOLANG_FUNC_INFO_PREFIX = "Golang function info: "; /** * Returns true if the specified {@link DWARFFunction} wrapper refers to a function in a golang @@ -73,33 +77,43 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup { return true; } + private GoRttiMapper goBinary; + @Override - public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { - if (!isGolangFunction(dfunc)) { - return; - } - GoVer goVersion = getGolangVersion(dfunc); - if (goVersion == GoVer.UNKNOWN) { + public void fixupDWARFFunction(DWARFFunction dfunc) throws DWARFException { + if (!isGolangFunction(dfunc) || !initGoBinaryContext(dfunc, TaskMonitor.DUMMY)) { return; } - DataTypeManager dtm = gfunc.getProgram().getDataTypeManager(); - GoParamStorageAllocator storageAllocator = - new GoParamStorageAllocator(gfunc.getProgram(), goVersion); + GoFuncData funcData = goBinary.getFunctionData(dfunc.address); + if (funcData == null) { + appendComment(dfunc.function, GOLANG_FUNC_INFO_PREFIX, "No function data"); + dfunc.signatureCommitMode = CommitMode.SKIP; + return; + } + if (!funcData.getFlags().isEmpty()) { + // Don't apply any DWARF info to special functions (ASM) as they are typically + // marked as no-params, but in reality they do have params passed in a non-standard way. + dfunc.signatureCommitMode = CommitMode.SKIP; + return; + } - if (GoFunctionFixup.isGolangAbi0Func(gfunc)) { + DataTypeManager dtm = goBinary.getProgram().getDataTypeManager(); + GoParamStorageAllocator storageAllocator = goBinary.newStorageAllocator(); + + if (goBinary.isGolangAbi0Func(dfunc.function)) { // Some (typically lower level) functions in the binary will be marked with a // symbol that ends in the string "abi0". // Throw away all registers and force stack allocation for everything storageAllocator.setAbi0Mode(); - dfunc.prototypeModel = storageAllocator.getAbi0CallingConvention(); - } - else { - dfunc.prototypeModel = storageAllocator.getAbiInternalCallingConvention(); } + dfunc.callingConventionName = + storageAllocator.isAbi0Mode() ? GoConstants.GOLANG_ABI0_CALLINGCONVENTION_NAME + : GoConstants.GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME; + GoFunctionMultiReturn multiReturnInfo = fixupFormalFuncDef(dfunc, storageAllocator, dtm); - fixupCustomStorage(dfunc, gfunc, storageAllocator, dtm, multiReturnInfo); + fixupCustomStorage(dfunc, storageAllocator, dtm, multiReturnInfo); } private GoFunctionMultiReturn fixupFormalFuncDef(DWARFFunction dfunc, @@ -109,10 +123,19 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup { // auto-assigned. // Pull them out of the param list and create a structure to hold them as the return value // They also need to be sorted so that stack storage items appear last, after register items. + // Note: sometimes Go will duplicate the dwarf information about return values, which + // will lead to have multiple "~r0", "~r1" elements. These need to be de-duped. List realParams = new ArrayList<>(); List returnParams = new ArrayList<>(); + Set returnParamNames = new HashSet<>(); for (DWARFVariable dvar : dfunc.params) { if (dvar.isOutputParameter) { + if (returnParamNames.contains(dvar.name.getName())) { + // skip this, its probably a duplicate. Golang github issue #61357 + continue; + } + returnParamNames.add(dvar.name.getName()); + returnParams.add(dvar); } else { @@ -136,9 +159,8 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup { return multiReturn; } - private void fixupCustomStorage(DWARFFunction dfunc, Function gfunc, - GoParamStorageAllocator storageAllocator, DataTypeManager dtm, - GoFunctionMultiReturn multiReturn) { + private void fixupCustomStorage(DWARFFunction dfunc, GoParamStorageAllocator storageAllocator, + DataTypeManager dtm, GoFunctionMultiReturn multiReturn) { // // This method implements the pseudo-code in // https://github.com/golang/go/blob/master/src/cmd/compile/abi-internal.md. @@ -200,9 +222,9 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup { storageAllocator, false); } - Program program = gfunc.getProgram(); + Program program = goBinary.getProgram(); if (!program.getMemory().isBigEndian()) { - // revserse the ordering of the storage varnodes when little-endian + // Reverse the ordering of the storage varnodes when little-endian List varnodes = dfunc.retval.getVarnodes(); GoFunctionFixup.reverseNonStackStorageLocations(varnodes); dfunc.retval.setVarnodes(varnodes); @@ -267,93 +289,26 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup { return returnResultVar; } -// /** -// * Create a structure that holds the multiple return values from a golang func. -// *

    -// * The contents of the structure may not be in the same order as the formal declaration, -// * but instead are ordered to make custom varnode storage work. -// *

    -// * Because stack varnodes must be placed in a certain order of storage, items that are -// * stack based are tagged with a text comment "stack" to allow storage to be correctly -// * recalculated later. -// * -// * @param returnParams -// * @param dfunc -// * @param dtm -// * @param storageAllocator -// * @return -// */ -// public static Structure createStructForReturnValues(List returnParams, -// DWARFFunction dfunc, DataTypeManager dtm, -// GoParamStorageAllocator storageAllocator) { -// -// String returnStructName = dfunc.name.getName() + MULTIVALUE_RETURNTYPE_SUFFIX; -// DWARFNameInfo structDNI = dfunc.name.replaceName(returnStructName, returnStructName); -// Structure struct = -// new StructureDataType(structDNI.getParentCP(), structDNI.getName(), 0, dtm); -// struct.setPackingEnabled(true); -// struct.setExplicitPackingValue(1); -// -// storageAllocator = storageAllocator.clone(); -// List stackResults = new ArrayList<>(); -// // TODO: zero-length items also need to be segregated at the end of the struct -// for (DWARFVariable dvar : returnParams) { -// List regs = storageAllocator.getRegistersFor(dvar.type); -// if (regs == null || regs.isEmpty()) { -// stackResults.add(dvar); -// } -// else { -// struct.add(dvar.type, dvar.name.getName(), regs.toString()); -// } -// } -// -// boolean be = dfunc.getProgram().isBigEndian(); -// -// // add these to the struct last or first, depending on endianness -// for (int i = 0; i < stackResults.size(); i++) { -// DWARFVariable dvar = stackResults.get(i); -// if (be) { -// struct.add(dvar.type, dvar.name.getName(), "stack"); -// } -// else { -// struct.insert(i, dvar.type, -1, dvar.name.getName(), "stack"); -// } -// } -// -// return struct; -// } - - private void exportOrigFuncDef(DWARFFunction dfunc, DataTypeManager dtm) { - try { - FunctionDefinition funcDef = dfunc.asFuncDef(); - funcDef.setCategoryPath(GOLANG_API_EXPORT); - dtm.addDataType(funcDef, DataTypeConflictHandler.KEEP_HANDLER); - } - catch (DuplicateNameException e) { - // skip - } - } - - private GoVer getGolangVersion(DWARFFunction dfunc) { - DWARFProgram dprog = dfunc.getProgram(); - GoVer ver = dprog.getOpaqueProperty(GoVer.class, null, GoVer.class); - if (ver == null) { - GoBuildInfo goBuildInfo = GoBuildInfo.fromProgram(dprog.getGhidraProgram()); - ver = goBuildInfo != null ? goBuildInfo.getVerEnum() : GoVer.UNKNOWN; - dprog.setOpaqueProperty(GoVer.class, ver); - } - return ver; - } - private static final String GOLANG_ZEROBASE_ADDR = "GOLANG_ZEROBASE_ADDR"; private Address getZerobaseAddress(DWARFFunction dfunc) { DWARFProgram dprog = dfunc.getProgram(); - Address zerobaseAddr = dprog.getOpaqueProperty(GOLANG_ZEROBASE_ADDR, null, Address.class); - if (zerobaseAddr == null) { - zerobaseAddr = GolangSymbolAnalyzer.getZerobaseAddress(dprog.getGhidraProgram()); - dprog.setOpaqueProperty(GOLANG_ZEROBASE_ADDR, zerobaseAddr); - } + Program program = dprog.getGhidraProgram(); + Address zerobaseAddr = TransientProgramProperties.getProperty(program, GOLANG_ZEROBASE_ADDR, + SCOPE.ANALYSIS_SESSION, Address.class, () -> GoRttiMapper.getZerobaseAddress(program)); return zerobaseAddr; } + + private boolean initGoBinaryContext(DWARFFunction dfunc, TaskMonitor monitor) { + if (goBinary == null) { + Program program = dfunc.getProgram().getGhidraProgram(); + goBinary = GoRttiMapper.getSharedGoBinary(program, monitor); + } + return goBinary != null; + } + + private void appendComment(Function func, String prefix, String comment) { + DWARFUtil.appendComment(goBinary.getProgram(), func.getEntryPoint(), CodeUnit.PLATE_COMMENT, + prefix, comment, "\n"); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/NoteGoBuildId.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/NoteGoBuildId.java index 140942532d..2cc1624498 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/NoteGoBuildId.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/NoteGoBuildId.java @@ -28,6 +28,7 @@ import ghidra.program.model.listing.Program; */ public class NoteGoBuildId extends ElfNote { public static final String SECTION_NAME = ".note.go.buildid"; + public static final String PROGRAM_INFO_KEY = "Golang BuildId"; /** * Reads a NoteGoBuildId from the specified BinaryReader, matching the signature of @@ -67,7 +68,7 @@ public class NoteGoBuildId extends ElfNote { @Override public String getProgramInfoKey() { - return "Golang BuildId"; + return PROGRAM_INFO_KEY; } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/PEGoBuildId.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/PEGoBuildId.java index d4bf745fad..f8056844bb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/PEGoBuildId.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/PEGoBuildId.java @@ -15,11 +15,10 @@ */ package ghidra.app.util.bin.format.golang; -import java.util.Arrays; - import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.ByteArrayProvider; @@ -105,7 +104,8 @@ public class PEGoBuildId implements ElfInfoItem { @Override public void markupProgram(Program program, Address address) { - program.getOptions(Program.PROGRAM_INFO).setString("Golang BuildId", getBuildId()); + program.getOptions(Program.PROGRAM_INFO) + .setString(NoteGoBuildId.PROGRAM_INFO_KEY, getBuildId()); try { StructureDataType struct = toStructure(program.getDataTypeManager()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java index 24f40c4be9..0733f9c580 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java @@ -16,14 +16,23 @@ package ghidra.app.util.bin.format.golang.rtti; import java.io.IOException; +import java.util.*; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.golang.rtti.types.GoMethod.GoMethodInfo; import ghidra.app.util.bin.format.golang.structmapping.*; -import ghidra.program.model.address.Address; -import ghidra.program.model.symbol.SymbolUtilities; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.Function; import ghidra.util.NumericUtilities; +import ghidra.util.exception.CancelledException; +/** + * A structure that golang generates that contains metadata about a function. + */ @StructureMapping(structureName = "runtime._func") public class GoFuncData implements StructureMarkup { + @ContextField private GoRttiMapper programContext; @@ -31,37 +40,239 @@ public class GoFuncData implements StructureMarkup { private StructureContext context; @FieldMapping(optional = true, fieldName = { "entryoff", "entryOff" }) - @EOLComment("description") - @MarkupReference("funcAddress") + @EOLComment("getDescription") + @MarkupReference("getFuncAddress") private long entryoff; // valid in >=1.18, relative offset of function @FieldMapping(optional = true) - @EOLComment("description") - @MarkupReference("funcAddress") + @EOLComment("getDescription") + @MarkupReference("getFuncAddress") private long entry; // valid in <=1.17, location of function @FieldMapping(fieldName = { "nameoff", "nameOff" }) - @MarkupReference("nameAddress") + @MarkupReference("getNameAddress") private long nameoff; + //private long args; // size of arguments + + @FieldMapping + private long pcfile; // offset in moduledata.pctab where file info starts + + @FieldMapping + private long pcln; // offset in moduledata.pctab where line num info starts + + @FieldMapping + private int npcdata; // number of elements in varlen pcdata array + + @FieldMapping + private long cuOffset; + + @FieldMapping + @EOLComment("getFuncIDEnum") + private byte funcID; // see GoFuncID enum + + @FieldMapping + @EOLComment("flags") + private byte flag; // runtime.funcFlag, see GoFuncFlag enum + + @FieldMapping + private int nfuncdata; // number of elements in varlen funcdata array + + //-------------------------------------------------------------------------------------- + private Address funcAddress; // set when entryoff or entry are set + /** + * Sets the function's entry point via a relative offset value + *

    + * Called via deserialization for entryoff fieldmapping annotation + * + * @param entryoff relative offset to function + */ public void setEntryoff(long entryoff) { this.entryoff = entryoff; GoModuledata moduledata = getModuledata(); this.funcAddress = moduledata != null ? moduledata.getText().add(entryoff) : null; + this.entry = funcAddress != null ? funcAddress.getOffset() : -1; } + /** + * Sets the absolute entry address. + *

    + * Called via deserialization for entry fieldmapping annotation + * + * @param entry absolute value. + */ public void setEntry(long entry) { this.entry = entry; this.funcAddress = context.getDataTypeMapper().getCodeAddress(entry); } + /** + * Returns the address of this function. + * + * @return the address of this function + */ public Address getFuncAddress() { return funcAddress; } + /** + * Returns the address range of this function's body, recovered by examining addresses in the + * function's pc-to-filename translation table, or if not present, a single address range + * that contains the function's entry point. + * + * @return {@link AddressRange} representing the function's known footprint + */ + public AddressRange getBody() { + // find the body of a function by looking at its pc-to-filename translation table and + // using the max pc value + try { + long max = new GoPcValueEvaluator(this, pcfile).getMaxPC() - 1; + return max > entry + ? new AddressRangeImpl(funcAddress, funcAddress.getNewAddress(max)) + : null; + } + catch (IOException e) { + return new AddressRangeImpl(getFuncAddress(), getFuncAddress()); + } + } + + /** + * Returns the Ghidra function that corresponds to this go function. + * + * @return Ghidra {@link Function}, or null if there is no Ghidra function at the address + */ + public Function getFunction() { + Address addr = getFuncAddress(); + return programContext.getProgram().getFunctionManager().getFunctionAt(addr); + } + + private long getPcDataStartOffset(int tableIndex) { + return context.getStructureLength() + (4 /*size(int32)*/ * tableIndex); + } + + private long getPcDataStart(int tableIndex) throws IOException { + return context.getFieldReader(getPcDataStartOffset(tableIndex)).readNextUnsignedInt(); + } + + private long getFuncDataPtr(int tableIndex) throws IOException { + // hacky, since both pcdata and funcdata are sequential int32[] arrays, just reuse logic + // for first one to index into second one + return getPcDataStart(npcdata + tableIndex); + } + + /** + * Returns a value from the specified pc->value lookup table, for a specific + * address (that should be within the function's footprint). + * + * @param tableIndex {@link GoPcDataTable} enum + * @param targetPC address (inside the function) to determine the value of + * @return int value, will be specific to the {@link GoPcDataTable table} it comes from, or + * -1 if the requested table index is not present for this function + * @throws IOException if error reading lookup data + */ + public int getPcDataValue(GoPcDataTable tableIndex, long targetPC) throws IOException { + if (tableIndex == null || tableIndex.ordinal() >= npcdata) { + return -1; + } + long pcstart = getPcDataStart(tableIndex.ordinal()); + return new GoPcValueEvaluator(this, pcstart).eval(targetPC); + } + + /** + * Returns all values for the specified pc->value lookup table for the entire range of the + * function's footprint. + * + * @param tableIndex {@link GoPcDataTable} enum + * @return list of int values, will be specific to the {@link GoPcDataTable table} it comes + * from, or an empty list if the requested table index is not present for this function + * @throws IOException if error reading lookup data + */ + public List getPcDataValues(GoPcDataTable tableIndex) throws IOException { + if (tableIndex == null || tableIndex.ordinal() >= npcdata) { + return List.of(); + } + long pcstart = getPcDataStart(tableIndex.ordinal()); + return new GoPcValueEvaluator(this, pcstart).evalAll(Long.MAX_VALUE); + } + + /** + * Returns a value associated with this function. + * + * @param tableIndex {@link GoFuncDataTable} enum + * @return requested value, or -1 if the requested table index is not present for this function + * @throws IOException if error reading lookup data + */ + public long getFuncDataValue(GoFuncDataTable tableIndex) throws IOException { + if (tableIndex == null || tableIndex.ordinal() < 0 || tableIndex.ordinal() >= nfuncdata) { + return -1; + } + long gofuncoffset = getModuledata().getGofunc(); + if (gofuncoffset == 0) { + return -1; + } + long off = getFuncDataPtr(tableIndex.ordinal()); + return off == -1 ? null : gofuncoffset + off; + } + + /** + * Attempts to build a 'function signature' string representing the known information about + * this function's arguments, using go's built-in stack trace metadata. + *

    + * The information that can be recovered about arguments is limited to: + *

      + *
    • the size of the argument + *
    • general grouping (eg. grouping of arg values as a structure or array) + *
    + * Return value information is unknown and always represented as an "undefined" data type. + * + * @return pseduo-function signature string, such as "undefined foo( 8, 8 )" which would + * indicate the function had 2 8-byte arguments + * @throws IOException if error reading lookup data + */ + public String recoverFunctionSignature() throws IOException { + RecoveredSignature sig = RecoveredSignature.read(this, programContext); + return sig.toString(); + } + + /** + * Attempts to return a {@link FunctionDefinition} for this function, based on this + * function's inclusion in a golang interface as a method. + * + * @return {@link FunctionDefinition} + * @throws IOException if error + */ + public FunctionDefinition findMethodSignature() throws IOException { + MethodInfo methodInfo = findMethodInfo(); + return methodInfo != null ? methodInfo.getSignature() : null; + } + + /** + * Attempts to return a {@link GoMethodInfo} for this function, based on this + * function's inclusion in a golang interface as a method. + * + * @return {@link GoMethodInfo} + */ + public GoMethodInfo findMethodInfo() { + for (MethodInfo methodInfo : programContext.getMethodInfoForFunction(funcAddress)) { + if (methodInfo instanceof GoMethodInfo gmi) { + if (gmi.isTfn(funcAddress)) { + return gmi; + } + } + } + return null; + } + + /** + * Returns the address of this function's name string. + *

    + * Referenced from nameoff's markup annotation + * + * @return {@link Address} + */ public Address getNameAddress() { GoModuledata moduledata = getModuledata(); return moduledata != null @@ -69,23 +280,109 @@ public class GoFuncData implements StructureMarkup { : null; } - public String getName() throws IOException { + /** + * Returns the name of this function. + * + * @return String name of this function + */ + public String getName() { GoModuledata moduledata = getModuledata(); - return moduledata != null - ? programContext.getReader(moduledata.getFuncnametab().getArrayOffset() + nameoff) - .readNextUtf8String() - : null; + try { + if (moduledata != null) { + return programContext + .getReader(moduledata.getFuncnametab().getArrayOffset() + nameoff) + .readNextUtf8String(); + } + } + catch (IOException e) { + // fall thru + } + return "unknown_func_%x_%s".formatted(context.getStructureStart(), + funcAddress != null ? funcAddress : "missing_addr"); } - public String getDescription() throws IOException { + /** + * Returns the name of this function, as a parsed symbol object. + * + * @return {@link GoSymbolName} containing this function's name + */ + public GoSymbolName getSymbolName() { + return GoSymbolName.parse(getName()); + } + + /** + * Returns a descriptive string. + *

    + * Referenced from the entry, entryoff field's markup annotation + * + * @return String description + */ + public String getDescription() { return getName() + "@" + getFuncAddress(); } + /** + * Returns true if this function is inline + * @return true if this function is inline + */ public boolean isInline() { return entryoff == -1 || entryoff == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG; } - private GoModuledata getModuledata() { + /** + * Returns the func flags for this function. + * + * @return {@link GoFuncFlag}s + */ + public Set getFlags() { + return GoFuncFlag.parseFlags(flag); + } + + /** + * Returns true if this function is an ASM function + * + * @return true if this function is an ASM function + */ + public boolean isAsmFunction() { + return GoFuncFlag.ASM.isSet(flag); + } + + /** + * Returns the {@link GoFuncID} enum that categorizes this function + * @return the {@link GoFuncID} enum that categorizes this function + */ + public GoFuncID getFuncIDEnum() { + return GoFuncID.parseIDByte(funcID); + } + + /** + * Returns information about the source file that this function was defined in. + * + * @return {@link GoSourceFileInfo}, or null if no source file info present + * @throws IOException if error reading lookup data + */ + public GoSourceFileInfo getSourceFileInfo() throws IOException { + GoModuledata moduledata = getModuledata(); + if (moduledata == null) { + return null; + } + int fileno = new GoPcValueEvaluator(this, pcfile).eval(entry); + int lineNum = new GoPcValueEvaluator(this, pcln).eval(entry); + + long fileoff = fileno >= 0 + ? moduledata.getCutab() + .readUIntElement(4 /*sizeof(uint32)*/, (int) cuOffset + fileno) + : -1; + String fileName = fileoff != -1 ? moduledata.getFilename(fileoff) : null; + return fileName != null ? new GoSourceFileInfo(fileName, lineNum) : null; + } + + /** + * Returns a reference to the {@link GoModuledata} that contains this function. + * + * @return {@link GoModuledata} that contains this function + */ + public GoModuledata getModuledata() { return programContext.findContainingModuleByFuncData(context.getStructureStart()); } @@ -100,10 +397,185 @@ public class GoFuncData implements StructureMarkup { } @Override - public void additionalMarkup(MarkupSession session) throws IOException { - Address addr = getFuncAddress(); - String name = SymbolUtilities.replaceInvalidChars(getName(), true); - session.createFunctionIfMissing(name, addr); + public void additionalMarkup(MarkupSession session) throws IOException, CancelledException { + if (npcdata > 0) { + ArrayDataType pcdataArrayDT = new ArrayDataType(programContext.getUint32DT(), npcdata, + -1, programContext.getDTM()); + Address addr = context.getStructureAddress().add(getPcDataStartOffset(0)); + session.markupAddress(addr, pcdataArrayDT); + session.labelAddress(addr, getStructureLabel() + "___pcdata"); + } + if (nfuncdata > 0) { + ArrayDataType funcdataArrayDT = new ArrayDataType(programContext.getUint32DT(), + nfuncdata, -1, programContext.getDTM()); + Address addr = context.getStructureAddress().add(getPcDataStartOffset(npcdata)); + session.markupAddress(addr, funcdataArrayDT); + session.labelAddress(addr, getStructureLabel() + "___funcdata"); + } + } + + //------------------------------------------------------------------------------------------- + + /** + * Represents approximate parameter signatures for a function. + *

    + * Golang's exception/stack-trace metadata is mined to provide these approximate signatures, + * and any limitation in the information recovered is due to what golang stores. + *

    + * Instead of data types, only the size and limited grouping of structure/array parameters + * is recoverable. + * + * @param returnType return type of the function (currently just undefined) + * @param name name of the function + * @param args list of recovered arguments + * @param partial boolean flag, if true there was an argument that was marked as partial + * @param error boolean flag, if true there was an error reading the argument info + * + */ + record RecoveredSignature(DataType returnType, String name, List args, + boolean partial, boolean error) { + + private static final int ARGINFO_ENDSEQ = 0xff; + private static final int ARGINFO_STARTAGG = 0xfe; + private static final int ARGINFO_ENDAGG = 0xfd; + private static final int ARGINFO_DOTDOTDOT = 0xfc; + private static final int ARGINFO_OFFSET_TOOLARGE = 0xfb; + + public static RecoveredSignature read(GoFuncData funcData, GoRttiMapper goBinary) + throws IOException { + RecoveredArg args = readArgs(funcData, goBinary); + return new RecoveredSignature(DataType.DEFAULT, funcData.getName(), args.subArgs, + args.hasPartialFlag(), args.partial); + } + + public static RecoveredArg readArgs(GoFuncData funcData, GoRttiMapper goBinary) + throws IOException { + long argInfoOffset = funcData.getFuncDataValue(GoFuncDataTable.FUNCDATA_ArgInfo); + if (argInfoOffset == -1) { + return new RecoveredArg(List.of(), 0, false); + } + BinaryReader argInfoReader = goBinary.getReader(argInfoOffset); + Deque> resultStack = new ArrayDeque<>(); + List parent = null; + List current = new ArrayList<>(); + List results = current; + try { + while (true) { + int b = argInfoReader.readNextUnsignedByte(); + switch (b) { + case ARGINFO_ENDSEQ: + return new RecoveredArg(results, 0, false); + case ARGINFO_STARTAGG: + parent = current; + current = new ArrayList<>(); + resultStack.addLast(current); + break; + case ARGINFO_ENDAGG: + if (parent == null) { + throw new IOException("no parent"); + } + parent.add(new RecoveredArg(current, 0, false)); + current = parent; + parent = resultStack.pollLast(); + break; + case ARGINFO_DOTDOTDOT: + current.add(new RecoveredArg(null, -1, true)); + break; + case ARGINFO_OFFSET_TOOLARGE: + current.add(new RecoveredArg(null, -2, true)); + break; + default: + // b == 'offset', but value doesn't seem to be consistently useful + int sz = argInfoReader.readNextUnsignedByte(); + current.add(new RecoveredArg(null, sz, false)); + break; + } + } + } + catch (IOException e) { + return new RecoveredArg(results, 0, true /* error flag */); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (partial) { + sb.append("[partial] "); + } + if (error) { + sb.append("[error] "); + } + + sb.append(returnType != null ? returnType.getName() : "???"); + sb.append(" ").append(name).append("("); + + boolean first = true; + for (RecoveredArg arg : args) { + if (!first) { + sb.append(", "); + } + first = false; + arg.concatString(sb); + } + + sb.append(")"); + + return sb.toString(); + } + } + + /** + * Represents the information recovered about a single argument. + * + * @param subArgs list of components if this arg is an aggregate, otherwise null + * @param argSize size of this arg if it primitive + * @param partial boolean flag, if true this arg was marked as a "..." or "_" arg + * + */ + record RecoveredArg(List subArgs, int argSize, boolean partial) { + + boolean hasPartialFlag() { + if (partial) { + return true; + } + if (subArgs != null) { + for (RecoveredArg subArg : subArgs) { + if (subArg.hasPartialFlag()) { + return true; + } + } + } + return false; + } + + void concatString(StringBuilder sb) { + if (subArgs != null) { + boolean first = true; + sb.append("struct? {"); + for (RecoveredArg subArg : subArgs) { + if (!first) { + sb.append(", "); + } + first = false; + subArg.concatString(sb); + } + sb.append("}"); + } + else { + sb.append(switch (argSize) { + case -1 -> "..."; + case -2 -> "???"; + default -> Integer.toString(argSize); + }); + } + } + } + + @Override + public String toString() { + return "GoFuncData [getFuncAddress()=%s, getSymbolName()=%s, getStructureContext()=%s]" + .formatted(getFuncAddress(), getSymbolName(), getStructureContext()); } } @@ -126,4 +598,6 @@ Length: 40 Alignment: 4 uint8 nfuncdata } pack() +int32[] pcdata +int32[] funcdata */ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncDataTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncDataTable.java new file mode 100644 index 0000000000..691d56fb9d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncDataTable.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.util.bin.format.golang.rtti; + +/** + * An index into a GoFuncData's variable-size funcdata array. See GoFuncData's nfuncdata for + * actual array size. + */ +public enum GoFuncDataTable { + FUNCDATA_ArgsPointerMaps, // 0 + FUNCDATA_LocalsPointerMaps, // 1 + FUNCDATA_StackObjects, // 2; + FUNCDATA_InlTree, // 3 + FUNCDATA_OpenCodedDeferInfo, // 4 + FUNCDATA_ArgInfo, // 5 + FUNCDATA_ArgLiveInfo, // 6 + FUNCDATA_WrapInfo // 7 +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncFlag.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncFlag.java new file mode 100644 index 0000000000..b51c1b2d8b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncFlag.java @@ -0,0 +1,54 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.util.bin.format.golang.rtti; + +import java.util.EnumSet; +import java.util.Set; + +/** + * Bitmask flags for runtime._func (GoFuncData) flags field. + */ +public enum GoFuncFlag { + + TOPFRAME(1 << 0), // 1 + SPWRITE(1 << 1), // 2 + ASM(1 << 2); // 4 + + private final int value; + + private GoFuncFlag(int i) { + this.value = i; + } + + public int getValue() { + return value; + } + + public boolean isSet(int i) { + return (i & value) != 0; + } + + public static Set parseFlags(int b) { + EnumSet result = EnumSet.noneOf(GoFuncFlag.class); + for (GoFuncFlag flag : values()) { + if (flag.isSet(b)) { + result.add(flag); + } + } + return result; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncID.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncID.java new file mode 100644 index 0000000000..791d8575f7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncID.java @@ -0,0 +1,49 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.util.bin.format.golang.rtti; + +/** + * List of function ids for runtime._func (GoFuncData) funcID field. + */ +public enum GoFuncID { + NORMAL, + ABORT, + ASMCGOCALL, + ASYNCPREEMPT, + CGOCALLBACK, + DEBUGCALLV2, + GCBGMARKWORKER, + GOEXIT, + GOGO, + GOPANIC, + HANDLEASYNCEVENT, + MCALL, + MORESTACK, + MSTART, + PANICWRAP, + RT0_GO, + RUNFINQ, + RUNTIME_MAIN, + SIGPANIC, + SYSTEMSTACK, + SYSTEMSTACK_SWITCH, + WRAPPER; + + public static GoFuncID parseIDByte(int b) { + GoFuncID[] values = values(); + return 0 <= b && b < values.length ? values[b] : null; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java index e2d264597c..7c7d5933bb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java @@ -20,6 +20,10 @@ import java.io.IOException; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.program.model.address.Address; +/** + * A structure that golang generates that maps between a function's entry point and the + * location of the function's GoFuncData structure. + */ @StructureMapping(structureName = "runtime.functab") public class GoFunctabEntry { @ContextField @@ -29,19 +33,26 @@ public class GoFunctabEntry { private StructureContext context; @FieldMapping(optional = true) - @MarkupReference("funcAddress") + @MarkupReference("getFuncAddress") private long entryoff; // valid in >=1.18, relative offset of function @FieldMapping(optional = true) - @MarkupReference("funcAddress") + @MarkupReference("getFuncAddress") private long entry; // valid in <=1.17, location of function @FieldMapping - @MarkupReference("funcData") + @MarkupReference("getFuncData") private long funcoff; // offset into pclntable -> _func private Address funcAddress; + /** + * Set the function's entry point using a relative offset. + *

    + * Called via deserialization for entryoff fieldmapping annotation. + * + * @param entryoff relative offset of the function's entry point + */ public void setEntryoff(long entryoff) { this.entryoff = entryoff; @@ -49,15 +60,33 @@ public class GoFunctabEntry { this.funcAddress = moduledata != null ? moduledata.getText().add(entryoff) : null; } + /** + * Set the function's entry point using the absolute address. + *

    + * Called via deserialization for entry fieldmapping annotation. + * + * @param entry address of the function's entry point + */ public void setEntry(long entry) { this.entry = entry; this.funcAddress = programContext.getCodeAddress(entry); } + /** + * Returns the address of the function's entry point + * + * @return address of the function's entry point + */ public Address getFuncAddress() { return funcAddress; } + /** + * Return the GoFuncData structure that contains metadata about the function. + * + * @return {@link GoFuncData} structure that contains metadata about the function. + * @throws IOException if error + */ @Markup public GoFuncData getFuncData() throws IOException { GoModuledata moduledata = getModuledata(); @@ -66,6 +95,11 @@ public class GoFunctabEntry { : null; } + /** + * Returns the offset of the GoFuncData structure. + * + * @return offset of the GoFuncData structure. + */ public long getFuncoff() { return funcoff; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoIface.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoIface.java index f9a81a5555..d089dfc17b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoIface.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoIface.java @@ -19,6 +19,9 @@ import java.io.IOException; import ghidra.app.util.bin.format.golang.structmapping.*; +/** + * A structure that golang generates that maps between a interface and its data + */ @StructureMapping(structureName = "runtime.iface") public class GoIface { @ContextField @@ -28,7 +31,7 @@ public class GoIface { private StructureContext context; @FieldMapping - @MarkupReference("itab") + @MarkupReference("getItab") long tab; // runtime.itab * @FieldMapping @@ -39,4 +42,15 @@ public class GoIface { return programContext.readStructure(GoItab.class, tab); } + @Override + public String toString() { + try { + return "GoIface { offset: %x, type: %s }" + .formatted(context != null ? context.getStructureStart() : 0, getItab()); + } + catch (IOException e) { + return "GoIface { %x, %x }".formatted(tab, data); + } + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoItab.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoItab.java index 3b3cc34b02..3a4007e3ca 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoItab.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoItab.java @@ -16,16 +16,21 @@ package ghidra.app.util.bin.format.golang.rtti; import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import java.util.*; +import java.util.Map.Entry; -import ghidra.app.util.bin.format.golang.rtti.types.GoInterfaceType; -import ghidra.app.util.bin.format.golang.rtti.types.GoType; +import ghidra.app.util.bin.format.golang.rtti.types.*; +import ghidra.app.util.bin.format.golang.rtti.types.GoIMethod.GoIMethodInfo; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.program.model.address.Address; import ghidra.program.model.data.DataType; +import ghidra.program.model.data.FunctionDefinition; +import ghidra.util.Msg; +/** + * Represents a mapping between a golang interface and a type that implements the methods of + * the interface. + */ @PlateComment @StructureMapping(structureName = "runtime.itab") public class GoItab implements StructureMarkup { @@ -36,41 +41,130 @@ public class GoItab implements StructureMarkup { private StructureContext context; @FieldMapping - @MarkupReference("interfaceType") + @MarkupReference("getInterfaceType") long inter; // runtime.interfacetype * @FieldMapping - @MarkupReference("type") + @MarkupReference("getType") long _type; // runtime._type * @FieldMapping long fun; // inline varlen array, specd as uintptr[1], we are treating as simple long + /** + * Returns the interface implemented by the specified type. + * + * @return interface implemented by the specified type + * @throws IOException if error reading ref'd interface structure + */ @Markup public GoInterfaceType getInterfaceType() throws IOException { return programContext.readStructure(GoInterfaceType.class, inter); } + /** + * Returns the type that implements the specified interface. + * + * @return type that implements the specified interface + * @throws IOException if error reading the ref'd type structure + */ @Markup public GoType getType() throws IOException { return programContext.getGoType(_type); } + /** + * Return the number of methods implemented. + * + * @return number of methods implemented + * @throws IOException if error reading interface structure + */ public long getFuncCount() throws IOException { GoInterfaceType iface = getInterfaceType(); GoSlice methods = iface.getMethodsSlice(); return Math.max(1, methods.getLen()); } + /** + * Returns an artificial slice that contains the address of the functions that implement + * the interface methods. + * + * @return artificial slice that contains the address of the functions that implement + * the interface methods + * @throws IOException if error reading method info + */ public GoSlice getFunSlice() throws IOException { long funcCount = getFuncCount(); long funOffset = context.getStructureEnd() - programContext.getPtrSize(); return new GoSlice(funOffset, funcCount, funcCount, programContext); } + private Map getInterfaceMethods() throws IOException { + long[] functionAddrs = getFunSlice().readUIntList(programContext.getPtrSize()); + GoInterfaceType iface = getInterfaceType(); + List ifaceMethods = iface.getMethods(); + if (functionAddrs.length != ifaceMethods.size()) { + Msg.warn(this, "Bad interface spec: %s, iface length doesn't match function impl list" + .formatted(getStructureLabel())); + return Map.of(); + } + Map results = new HashMap<>(); + for (int i = 0; i < functionAddrs.length; i++) { + if (functionAddrs[i] == 0) { + continue; + } + Address addr = programContext.getCodeAddress(functionAddrs[i]); + if (!programContext.getProgram() + .getMemory() + .getLoadedAndInitializedAddressSet() + .contains(addr)) { + continue; + } + GoIMethod imethod = ifaceMethods.get(i); + + results.put(addr, imethod); + } + return results; + } + + /** + * Returns list of {@link GoIMethodInfo} instances, that represent the methods implemented by + * the specified type / interface. + * + * @return list of {@link GoIMethodInfo} instances + * @throws IOException if error reading interface method list + */ + public List getMethodInfoList() throws IOException { + List results = new ArrayList<>(); + for (Entry entry : getInterfaceMethods().entrySet()) { + results.add(new GoIMethodInfo(this, entry.getValue(), entry.getKey())); + } + return results; + } + + /** + * Returns a {@link FunctionDefinition} for the specified method of this itab. + * + * @param imethod info about an interface method + * @return {@link FunctionDefinition} for the specified method of this itab + * @throws IOException if error reading required info + */ + public FunctionDefinition getSignatureFor(GoIMethod imethod) throws IOException { + GoType receiverType = getType(); + DataType receiverDT = programContext.getRecoveredType(receiverType); + + GoType methodType = imethod.getType(); + if (methodType == null) { + return null; + } + return programContext.getSpecializedMethodSignature(imethod.getName(), methodType, + receiverDT, false); + } + @Override public String getStructureName() throws IOException { - return getInterfaceType().getStructureName(); + return "%s__implements__%s".formatted(getType().getName(), + getInterfaceType().getName()); } @Override @@ -83,7 +177,7 @@ public class GoItab implements StructureMarkup { GoSlice funSlice = getFunSlice(); List

    funcAddrs = Arrays.stream(funSlice.readUIntList(programContext.getPtrSize())) .mapToObj(offset -> programContext.getCodeAddress(offset)) - .collect(Collectors.toList()); + .toList(); // this adds references from the elements of the artificial slice. However, the reference // from element[0] of the real "fun" array won't show anything in the UI even though // there is a outbound reference there. @@ -91,15 +185,17 @@ public class GoItab implements StructureMarkup { GoSlice extraFunSlice = funSlice.getSubSlice(1, funSlice.getLen() - 1, programContext.getPtrSize()); - extraFunSlice.markupArray(getStructureName() + "_extra_itab_functions", (DataType) null, - true, session); + extraFunSlice.markupArray(getStructureName() + "_extra_itab_functions", null, + (DataType) null, true, session); } @Override public String toString() { try { - String s = "itab for " + getStructureName(); GoInterfaceType ifaceType = getInterfaceType(); + String s = + "itab for %s implements %s".formatted(getType().getName(), + ifaceType.getName()); String methodListString = ifaceType.getMethodListString(); if (!methodListString.isEmpty()) { s += "\n// Methods\n" + methodListString; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java index f607fe2f80..2b8eb37cc1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java @@ -17,7 +17,6 @@ package ghidra.app.util.bin.format.golang.rtti; import java.io.IOException; import java.util.*; -import java.util.stream.Collectors; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.format.golang.rtti.types.GoType; @@ -31,10 +30,11 @@ import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.SymbolUtilities; import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; /** - * Represents a golang moduledata structure, which contains a lot of invaluable bootstrapping + * Represents a golang moduledata structure, which contains a lot of valuable bootstrapping * data for RTTI and function data. */ @StructureMapping(structureName = "runtime.moduledata") @@ -60,6 +60,9 @@ public class GoModuledata implements StructureMarkup { @FieldMapping(fieldName = "etypes") private long typesEndOffset; + @FieldMapping(optional = true) + private long gofunc; + @FieldMapping(fieldName = "typelinks") private GoSlice typeLinks; @@ -108,24 +111,62 @@ public class GoModuledata implements StructureMarkup { return programContext.readStructure(GoPcHeader.class, pcHeader); } + /** + * Returns the address of the beginning of the text section. + * + * @return address of the beginning of the text section + */ public Address getText() { return programContext.getCodeAddress(text); } + /** + * Returns the starting offset of type info + * + * @return starting offset of type info + */ public long getTypesOffset() { return typesOffset; } + /** + * Returns the ending offset of type info + * + * @return ending offset of type info + */ public long getTypesEndOffset() { return typesEndOffset; } + /** + * Return the offset of the gofunc location + * @return offset of the gofunc location + */ + public long getGofunc() { + return gofunc; + } + + /** + * Reads a {@link GoFuncData} structure from the pclntable. + * + * @param offset relative to the pclntable + * @return {@link GoFuncData} + * @throws IOException if error reading data + */ public GoFuncData getFuncDataInstance(long offset) throws IOException { return programContext.readStructure(GoFuncData.class, pclntable.getArrayOffset() + offset); } + /** + * Returns true if this GoModuleData is the module data that contains the specified + * GoFuncData structure. + * + * @param offset offset of a GoFuncData structure + * @return true if this GoModuleData is the module data that contains the specified GoFuncData + * structure + */ public boolean containsFuncDataInstance(long offset) { - return pclntable.isOffsetWithinData(offset, 1); + return pclntable.containsOffset(offset, 1); } /** @@ -143,9 +184,14 @@ public class GoModuledata implements StructureMarkup { return subSlice; } + /** + * Returns true if this module data structure contains sane values. + * + * @return true if this module data structure contains sane values + */ public boolean isValid() { MemoryBlock txtBlock = programContext.getProgram().getMemory().getBlock(".text"); - if (txtBlock != null && txtBlock.getStart().getOffset() != text) { + if (txtBlock != null && !txtBlock.contains(getText())) { return false; } @@ -164,10 +210,21 @@ public class GoModuledata implements StructureMarkup { return true; } + /** + * Returns a slice that contains all the function names. + * + * @return slice that contains all the function names + */ public GoSlice getFuncnametab() { return funcnametab; } + /** + * Returns a list of all functions contained in this module. + * + * @return list of all functions contained in this module + * @throws IOException if error reading data + */ public List getAllFunctionData() throws IOException { List functabentries = getFunctabEntriesSlice().readList(GoFunctabEntry.class); @@ -178,32 +235,86 @@ public class GoModuledata implements StructureMarkup { return result; } + /** + * Returns the cutab slice. + * + * @return cutab slice + */ + public GoSlice getCutab() { + return cutab; + } + + /** + * Returns the filetab slice. + * + * @return filetab slice + */ + public GoSlice getFiletab() { + return filetab; + } + + /** + * Returns the filename at the specified offset. + * + * @param fileoff offset in the filetab of the filename + * @return filename + * @throws IOException if error reading + */ + public String getFilename(long fileoff) throws IOException { + return programContext.getReader(filetab.getElementOffset(1, fileoff)).readNextUtf8String(); + } + + /** + * Returns the pctab slice. + * + * @return pctab slice + */ + public GoSlice getPctab() { + return pctab; + } + + /** + * Returns a reference to the controlling {@link GoRttiMapper go binary} context. + * + * @return reference to the controlling {@link GoRttiMapper go binary} context + */ + public GoRttiMapper getGoBinary() { + return programContext; + } + @Override public StructureContext getStructureContext() { return structureContext; } @Override - public void additionalMarkup(MarkupSession session) throws IOException { - typeLinks.markupArray("moduledata.typeLinks", programContext.getInt32DT(), false, session); + public void additionalMarkup(MarkupSession session) throws IOException, CancelledException { + typeLinks.markupArray("moduledata.typeLinks", null, programContext.getInt32DT(), false, + session); typeLinks.markupElementReferences(4, getTypeList(), session); - itablinks.markupArray("moduledata.itablinks", GoItab.class, true, session); + itablinks.markupArray("moduledata.itablinks", null, GoItab.class, true, session); markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session); markupStringTable(filetab.getArrayAddress(), filetab.getLen(), session); GoSlice subSlice = getFunctabEntriesSlice(); - subSlice.markupArray("moduledata.ftab", GoFunctabEntry.class, false, session); + subSlice.markupArray("moduledata.ftab", null, GoFunctabEntry.class, false, session); subSlice.markupArrayElements(GoFunctabEntry.class, session); Structure textsectDT = programContext.getGhidraDataType("runtime.textsect", Structure.class); if (textsectDT != null) { - textsectmap.markupArray("runtime.textsectionmap", textsectDT, false, session); + textsectmap.markupArray("runtime.textsectionmap", null, textsectDT, false, session); } } + /** + * Returns a list of the GoItabs present in this module. + * + * @return list of the GoItabs present in this module + * @throws IOException if error reading data + */ @Markup public List getItabs() throws IOException { List result = new ArrayList<>(); @@ -238,6 +349,12 @@ public class GoModuledata implements StructureMarkup { } } + /** + * Returns an iterator that walks all the types contained in this module + * + * @return iterator that walks all the types contained in this module + * @throws IOException if error reading data + */ @Markup public Iterator iterateTypes() throws IOException { return getTypeList().stream() @@ -253,12 +370,18 @@ public class GoModuledata implements StructureMarkup { .iterator(); } + /** + * Returns a list of locations of the types contained in this module. + * + * @return list of addresses of GoType structures + * @throws IOException if error reading data + */ public List
    getTypeList() throws IOException { long[] typeOffsets = typeLinks.readUIntList(4 /* always sizeof(int32) */); Address typesBaseAddr = programContext.getDataAddress(typesOffset); List
    result = Arrays.stream(typeOffsets) .mapToObj(offset -> typesBaseAddr.add(offset)) - .collect(Collectors.toList()); + .toList(); return result; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoName.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoName.java index 984d1d3969..915102169f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoName.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoName.java @@ -15,11 +15,10 @@ */ package ghidra.app.util.bin.format.golang.rtti; +import java.io.IOException; import java.util.EnumSet; import java.util.Set; -import java.io.IOException; - import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.program.model.data.DataType; @@ -49,7 +48,7 @@ public class GoName implements StructureReader, StructureMarkup private final int flagValue; - private Flag(int flagValue) { + Flag(int flagValue) { this.flagValue = flagValue; } @@ -80,14 +79,14 @@ public class GoName implements StructureReader, StructureMarkup int flags; @FieldOutput(isVariableLength = true) - @EOLComment("fullNameString") + @EOLComment("getFullNameString") GoVarlenString name; @FieldOutput(isVariableLength = true) GoVarlenString tag; - @FieldOutput(isVariableLength = true, getter = "pkgPathDataType") - @MarkupReference("pkgPath") + @FieldOutput(isVariableLength = true, getter = "getPkgPathDataType") + @MarkupReference("getPkgPath") long pkgPath; // uint32, nameoffset, only present if flags.HAS_PKGPATH @Override @@ -102,34 +101,78 @@ public class GoName implements StructureReader, StructureMarkup : 0; } + /** + * Returns the name value. + * + * @return name string + */ public String getName() { return name.getString(); } + /** + * Returns the tag string. + * + * @return tag string + */ public String getTag() { return tag != null ? tag.getString() : ""; } + /** + * Returns the package path string, or null if not present. + * + * @return package path string, or null if not present + * @throws IOException if error reading data + */ @Markup public GoName getPkgPath() throws IOException { return programContext.resolveNameOff(context.getStructureStart(), pkgPath); } + /** + * Returns the data type needed to store the pkg path offset field, called by serialization + * from the fieldoutput annotation. + * + * @return Ghidra data type needed to store the pkg path offset field, or null if not present + */ public DataType getPkgPathDataType() { return Flag.HAS_PKGPATH.isSet(flags) ? programContext.getInt32DT() : null; } - public String getFullNameString() throws IOException { - GoName pkgPathName = getPkgPath(); - return (pkgPathName != null ? pkgPathName.getFullNameString() + "." : "") + getName(); + /** + * Returns a descriptive string containing the full name value. + * + * @return descriptive string + */ + public String getFullNameString() { + String packagePathString = ""; + try { + GoName pkgPathName = getPkgPath(); + packagePathString = pkgPathName != null ? pkgPathName.getFullNameString() + "." : ""; + } + catch (IOException e) { + // fall thru with empty package path + } + return packagePathString + getName(); } + /** + * Returns the flags found in this structure. + * + * @return flags, as an int + */ public int getFlags() { return flags; } + /** + * Returns the flags found in this structure. + * + * @return flags, as a set of {@link Flag} enum values + */ public Set getFlagsSet() { return Flag.parseFlags(flags); } @@ -144,4 +187,28 @@ public class GoName implements StructureReader, StructureMarkup return getName(); } + @Override + public String toString() { + return String.format( + "GoName [context=%s, flags=%s, name=%s, tag=%s, pkgPath=%s, getFullNameString(): %s]", + context, flags, name, tag, pkgPath, getFullNameString()); + } + + //--------------------------------------------------------------------------------------------- + + /** + * Create a GoName instance that supplies a specified name. + * + * @param fakeName string name to return from the GoName's getName() + * @return new GoName instance that can only be used to call getName() + */ + public static GoName createFakeInstance(String fakeName) { + return new GoName() { + @Override + public String getName() { + return fakeName; + } + }; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisState.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcDataTable.java similarity index 65% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisState.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcDataTable.java index b2362e6a02..596ee2d2e6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisState.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcDataTable.java @@ -13,11 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.plugin.core.analysis; +package ghidra.app.util.bin.format.golang.rtti; /** - * Marker interface for program-specific state information held by {@link AnalysisStateInfo} + * An index into a GoFuncData's variable-sized pcdata array. See GoFuncData's npcdata field + * for the actual array size. */ -public interface AnalysisState { - // marker interface +public enum GoPcDataTable { + PCDATA_UnsafePoint, // 0 + PCDATA_StackMapIndex, // 1 + PCDATA_InlTreeIndex, // 2 + PCDATA_ArgLiveIndex // 3 } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java index 9a5e327c1f..cc7c620105 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java @@ -148,9 +148,12 @@ public class GoPcHeader { private StructureContext context; @FieldMapping - @EOLComment("goVersion") + @EOLComment("getGoVersion") private int magic; + @FieldMapping + private byte minLC; + @FieldMapping private byte ptrSize; @@ -159,23 +162,23 @@ public class GoPcHeader { private long textStart; // should be same as offset of ".text" @FieldMapping - @MarkupReference("funcnameAddress") + @MarkupReference("getFuncnameAddress") private long funcnameOffset; @FieldMapping - @MarkupReference("cuAddress") + @MarkupReference("getCuAddress") private long cuOffset; @FieldMapping - @MarkupReference("filetabAddress") + @MarkupReference("getFiletabAddress") private long filetabOffset; @FieldMapping - @MarkupReference("pctabAddress") + @MarkupReference("getPctabAddress") private long pctabOffset; @FieldMapping - @MarkupReference("pclnAddress") + @MarkupReference("getPclnAddress") private long pclnOffset; public GoVer getGoVersion() { @@ -190,34 +193,79 @@ public class GoPcHeader { return ver; } + /** + * Returns true if this pcln structure contains a textStart value (only present >= 1.18) + * @return + */ public boolean hasTextStart() { return textStart != 0; } + /** + * Returns the address of where the text area starts. + * + * @return address of text starts + */ public Address getTextStart() { return programContext.getDataAddress(textStart); } + /** + * Returns address of the func name slice + * @return address of func name slice + */ public Address getFuncnameAddress() { return programContext.getDataAddress(context.getStructureStart() + funcnameOffset); } + /** + * Returns address of the cu tab slice, used by the cuOffset field's markup annotation. + * @return address of the cu tab slice + */ public Address getCuAddress() { return programContext.getDataAddress(context.getStructureStart() + cuOffset); } + /** + * Returns the address of the filetab slice, used by the filetabOffset field's markup annotation + * @return address of the filetab slice + */ public Address getFiletabAddress() { return programContext.getDataAddress(context.getStructureStart() + filetabOffset); } + /** + * Returns the address of the pctab slice, used by the pctabOffset field's markup annotation + * @return address of the pctab slice + */ public Address getPctabAddress() { return programContext.getDataAddress(context.getStructureStart() + pctabOffset); } + /** + * Returns the address of the pcln slice, used by the pclnOffset field's markup annotation + * @return address of the pcln slice + */ public Address getPclnAddress() { return programContext.getDataAddress(context.getStructureStart() + pclnOffset); } + /** + * Returns the min lc, used as the GoPcValueEvaluator's pcquantum + * @return minLc + */ + public byte getMinLC() { + return minLC; + } + + /** + * Returns the pointer size + * @return pointer size + */ + public byte getPtrSize() { + return ptrSize; + } + //-------------------------------------------------------------------------------------------- record GoVerEndian(GoVer goVer, Endian endian) { GoVerEndian(GoVer goVer, boolean isLittleEndian) { @@ -242,3 +290,22 @@ public class GoPcHeader { } } +/* +struct runtime.pcHeader +Length: 40 Alignment: 4 +{ + uint32 magic + uint8 pad1 + uint8 pad2 + uint8 minLC + uint8 ptrSize + int nfunc + uint nfiles + uintptr textStart + uintptr funcnameOffset + uintptr cuOffset + uintptr filetabOffset + uintptr pctabOffset + uintptr pclnOffset +} pack() +*/ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcValueEvaluator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcValueEvaluator.java new file mode 100644 index 0000000000..5bf66867b5 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcValueEvaluator.java @@ -0,0 +1,113 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.util.bin.format.golang.rtti; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.LEB128; + +/** + * Evaluates a sequence of (value_delta,pc_delta) leb128 pairs to calculate a value for a certain + * PC location. + */ +public class GoPcValueEvaluator { + private final int pcquantum; + private final long funcEntry; + private final BinaryReader reader; + + private int value = -1; + private long pc; + + /** + * Creates a {@link GoPcValueEvaluator} instance, tied to the specified GoFuncData, starting + * at the specified offset in the moduledata's pctab. + * + * @param func {@link GoFuncData} + * @param offset offset in moduledata's pctab + * @throws IOException if error reading pctab + */ + public GoPcValueEvaluator(GoFuncData func, long offset) throws IOException { + GoModuledata moduledata = func.getModuledata(); + + this.pcquantum = moduledata.getGoBinary().getMinLC(); + this.reader = moduledata.getPctab().getElementReader(1, (int) offset); + + this.funcEntry = func.getFuncAddress().getOffset(); + this.pc = funcEntry; + } + + /** + * Returns the largest PC value calculated when evaluating the result of the table's sequence. + * + * @return largest PC value encountered + * @throws IOException if error evaluating result + */ + public long getMaxPC() throws IOException { + eval(Long.MAX_VALUE); + return pc; + } + + /** + * Returns the value encoded into the table at the specified pc. + * + * @param targetPC pc + * @return value at specified pc, or -1 if error evaluating table + * @throws IOException if error reading data + */ + public int eval(long targetPC) throws IOException { + while (pc <= targetPC) { + if (!step()) { + return -1; + } + } + return value; + } + + /** + * Returns the set of all values for each unique pc section. + * + * @param targetPC max pc to advance the sequence to when evaluating the table + * @return list of integer values + * @throws IOException if error reading data + */ + public List evalAll(long targetPC) throws IOException { + List result = new ArrayList(); + while (pc <= targetPC) { + if (!step()) { + return result; + } + result.add(value); + } + return result; + } + + private boolean step() throws IOException { + int uvdelta = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + if (uvdelta == 0 && pc != funcEntry) { + // a delta of 0 is only valid on the first element + return false; + } + value += -(uvdelta & 1) ^ (uvdelta >> 1); + + int pcdelta = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + pc += pcdelta * pcquantum; + + return true; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java index 67a15279dd..e08c1138f3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java @@ -15,37 +15,40 @@ */ package ghidra.app.util.bin.format.golang.rtti; +import static java.util.stream.Collectors.*; + import java.io.File; import java.io.IOException; import java.util.*; import java.util.Map.Entry; -import java.util.stream.Collectors; + +import javax.help.UnsupportedOperationException; import generic.jar.ResourceFile; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.app.plugin.core.analysis.TransientProgramProperties; import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility; import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf4.next.DWARFDataTypeConflictHandler; import ghidra.app.util.bin.format.dwarf4.next.DWARFProgram; import ghidra.app.util.bin.format.golang.*; import ghidra.app.util.bin.format.golang.rtti.types.*; -import ghidra.app.util.bin.format.golang.structmapping.DataTypeMapper; -import ghidra.app.util.bin.format.golang.structmapping.StructureMapping; +import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.app.util.importer.MessageLog; import ghidra.app.util.opinion.ElfLoader; import ghidra.app.util.opinion.PeLoader; +import ghidra.framework.store.LockException; import ghidra.program.model.address.*; import ghidra.program.model.data.*; -import ghidra.program.model.lang.Endian; -import ghidra.program.model.lang.PrototypeModel; -import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.Program; +import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult; +import ghidra.program.model.data.StandAloneDataTypeManager.LanguageUpdateOption; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlock; -import ghidra.program.model.symbol.Symbol; -import ghidra.program.model.symbol.SymbolType; -import ghidra.util.Msg; -import ghidra.util.NumericUtilities; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.DuplicateNameException; +import ghidra.program.model.symbol.*; +import ghidra.util.*; +import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; import ghidra.util.task.UnknownProgressWrappingTaskMonitor; @@ -77,16 +80,79 @@ import ghidra.util.task.UnknownProgressWrappingTaskMonitor; */ public class GoRttiMapper extends DataTypeMapper { + private static final String FAILED_FLAG = "FAILED TO FIND GOLANG BINARY"; + /** - * Returns a new {@link GoRttiMapper} for the specified program, or null if the binary + * Returns a shared {@link GoRttiMapper} for the specified program, or null if the binary * is not a supported golang binary. + *

    + * The returned value will be cached and returned in any additional calls to this method, and + * automatically {@link #close() closed} when the current analysis session is finished. + *

    + * NOTE: Only valid during an analysis session. If outside of an analysis session, use + * {@link #getGoBinary(Program)} to create a new instance if you need to use this outside + * of an analyzer. + * + * @param program golang {@link Program} + * @param monitor {@link TaskMonitor} + * @return a shared {@link GoRttiMapper go binary} instance, or null if unable to find valid + * golang info in the Program + * + */ + public static GoRttiMapper getSharedGoBinary(Program program, TaskMonitor monitor) { + if (TransientProgramProperties.hasProperty(program, FAILED_FLAG)) { + // don't try to do any work if we've failed earlier + return null; + } + GoRttiMapper goBinary = TransientProgramProperties.getProperty(program, GoRttiMapper.class, + TransientProgramProperties.SCOPE.ANALYSIS_SESSION, GoRttiMapper.class, () -> { + // cached instance not found, create new instance + Msg.info(GoRttiMapper.class, "Reading golang binary info: " + program.getName()); + try { + GoRttiMapper supplier_result = getGoBinary(program); + supplier_result.init(monitor); + return supplier_result; + } + catch (IllegalArgumentException | IOException e) { + TransientProgramProperties.getProperty(program, FAILED_FLAG, + TransientProgramProperties.SCOPE.PROGRAM, Boolean.class, () -> true); // also sets it + + if (e instanceof IOException) { + // this is a more serious error, and the stack trace should be written + // to the application log + Msg.error(GoRttiMapper.class, + "Failed to read golang info for: " + program.getName(), e); + + } + AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program); + if (aam.isAnalyzing()) { + // should cause a modal popup at end of analysis that the go binary wasn't + // supported + MessageLog log = aam.getMessageLog(); + log.appendMsg(e.getMessage()); + } + else { + Msg.warn(GoRttiMapper.class, "Golang program: " + e.getMessage()); + } + + return null; + } + }); + + return goBinary; + } + + /** + * Creates a {@link GoRttiMapper} representing the specified program. * * @param program {@link Program} - * @param log {@link MessageLog} - * @return new {@link GoRttiMapper}, or null if not a golang binary - * @throws IOException if bootstrap gdt is corrupted or some other struct mapping logic error + * @return new {@link GoRttiMapper}, or null if basic golang information is not found in the + * binary + * @throws IllegalArgumentException if the golang binary is an unsupported version + * @throws IOException if there was an error in the Ghidra golang rtti reading logic */ - public static GoRttiMapper getMapperFor(Program program, MessageLog log) throws IOException { + public static GoRttiMapper getGoBinary(Program program) + throws IllegalArgumentException, IOException { GoBuildInfo buildInfo = GoBuildInfo.fromProgram(program); GoVer goVer; if (buildInfo == null || (goVer = buildInfo.getVerEnum()) == GoVer.UNKNOWN) { @@ -98,15 +164,8 @@ public class GoRttiMapper extends DataTypeMapper { Msg.error(GoRttiMapper.class, "Missing golang gdt archive for " + goVer); } - try { - return new GoRttiMapper(program, buildInfo.getPointerSize(), buildInfo.getEndian(), - buildInfo.getVerEnum(), gdtFile); - } - catch (IllegalArgumentException e) { - // user deserves a warning because the binary wasn't supported - log.appendMsg(e.getMessage()); - return null; - } + return new GoRttiMapper(program, buildInfo.getPointerSize(), buildInfo.getEndian(), + buildInfo.getVerEnum(), gdtFile); } /** @@ -119,12 +178,9 @@ public class GoRttiMapper extends DataTypeMapper { * @return String, "golang_1.18_64bit_any.gdt" */ public static String getGDTFilename(GoVer goVer, int pointerSizeInBytes, String osName) { - String bitSize = pointerSizeInBytes > 0 - ? Integer.toString(pointerSizeInBytes * 8) - : "any"; - String gdtFilename = - "golang_%d.%d_%sbit_%s.gdt".formatted(goVer.getMajor(), goVer.getMinor(), - bitSize, osName); + String bitSize = pointerSizeInBytes > 0 ? Integer.toString(pointerSizeInBytes * 8) : "any"; + String gdtFilename = "golang_%d.%d_%sbit_%s.gdt".formatted(goVer.getMajor(), + goVer.getMinor(), bitSize, osName); return gdtFilename; } @@ -174,7 +230,48 @@ public class GoRttiMapper extends DataTypeMapper { return result; } - private static final CategoryPath RECOVERED_TYPES_CP = new CategoryPath("/golang-recovered"); + /** + * Returns true if the specified Program is marked as "golang". + * + * @param program {@link Program} + * @return boolean true if program is marked as golang + */ + public static boolean isGolangProgram(Program program) { + return GoConstants.GOLANG_CSPEC_NAME.equals( + program.getCompilerSpec().getCompilerSpecDescription().getCompilerSpecName()); + } + + /** + * Return the address of the golang zerobase symbol, or an artificial substitute. + *

    + * The zerobase symbol is used as the location of parameters that are zero-length. + * + * @param prog {@link Program} + * @return {@link Address} of the runtime.zerobase, or artificial substitute + */ + public static Address getZerobaseAddress(Program prog) { + Symbol zerobaseSym = SymbolUtilities.getUniqueSymbol(prog, "runtime.zerobase"); + Address zerobaseAddr = + zerobaseSym != null ? zerobaseSym.getAddress() : getArtificalZerobaseAddress(prog); + if (zerobaseAddr == null) { + zerobaseAddr = prog.getImageBase().getAddressSpace().getMinAddress(); // ICKY HACK + Msg.warn(GoFunctionFixup.class, + "Unable to find Golang runtime.zerobase, using " + zerobaseAddr); + } + return zerobaseAddr; + } + + public final static String ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME = + "ARTIFICIAL.runtime.zerobase"; + + private static Address getArtificalZerobaseAddress(Program program) { + Symbol zerobaseSym = + SymbolUtilities.getUniqueSymbol(program, ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME); + return zerobaseSym != null ? zerobaseSym.getAddress() : null; + } + + private static final CategoryPath RECOVERED_TYPES_CP = + GoConstants.GOLANG_RECOVERED_TYPES_CATEGORYPATH; private static final CategoryPath GOLANG_CP = GoConstants.GOLANG_CATEGORYPATH; private static final CategoryPath VARLEN_STRUCTS_CP = GoConstants.GOLANG_CATEGORYPATH; @@ -198,19 +295,21 @@ public class GoRttiMapper extends DataTypeMapper { private final DataType uintptrDT; private final DataType int32DT; private final DataType uint32DT; + private final DataType uint8DT; + private final DataType stringDT; private final Map goTypes = new HashMap<>(); private final Map typeNameIndex = new HashMap<>(); + private final Map fixedGoTypeNames = new HashMap<>(); private final Map cachedRecoveredDataTypes = new HashMap<>(); private final List modules = new ArrayList<>(); private Map funcdataByAddr = new HashMap<>(); private Map funcdataByName = new HashMap<>(); + private Map> methodsByAddr = new HashMap<>(); + private Map> interfacesImplementedByType = new HashMap<>(); + private byte minLC; private GoType mapGoType; private GoType chanGoType; private GoRegisterInfo regInfo; - private PrototypeModel abiInternalCallingConvention; - private PrototypeModel abi0CallingConvention; - private PrototypeModel duffzeroCallingConvention; - private PrototypeModel duffcopyCallingConvention; /** * Creates a GoRttiMapper using the specified bootstrap information. @@ -245,6 +344,9 @@ public class GoRttiMapper extends DataTypeMapper { AbstractIntegerDataType.getSignedDataType(4, null)); this.uint32DT = getTypeOrDefault("uint32", DataType.class, AbstractIntegerDataType.getUnsignedDataType(4, null)); + this.uint8DT = getTypeOrDefault("uint8", DataType.class, + AbstractIntegerDataType.getUnsignedDataType(1, null)); + this.stringDT = getTypeOrDefault("string", Structure.class, null); try { registerStructures(GOLANG_STRUCTMAPPED_CLASSES); @@ -271,27 +373,32 @@ public class GoRttiMapper extends DataTypeMapper { return goVersion; } + /** + * Returns a shared {@link GoRegisterInfo} instance + * @return {@link GoRegisterInfo} + */ public GoRegisterInfo getRegInfo() { return regInfo; } + /** + * Finishes making this instance ready to be used. + * + * @param monitor {@link TaskMonitor} + * @throws IOException if error reading data + */ public void init(TaskMonitor monitor) throws IOException { - initHiddenCompilerTypes(); - this.regInfo = GoRegisterInfoManager.getInstance() .getRegisterInfoForLang(program.getLanguage(), goVersion); - this.abiInternalCallingConvention = program.getFunctionManager() - .getCallingConvention(GoConstants.GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME); - this.abi0CallingConvention = program.getFunctionManager() - .getCallingConvention(GoConstants.GOLANG_ABI0_CALLINGCONVENTION_NAME); - this.duffzeroCallingConvention = program.getFunctionManager() - .getCallingConvention(GoConstants.GOLANG_DUFFZERO_CALLINGCONVENTION_NAME); - this.duffcopyCallingConvention = program.getFunctionManager() - .getCallingConvention(GoConstants.GOLANG_DUFFCOPY_CALLINGCONVENTION_NAME); - GoModuledata firstModule = findFirstModuledata(monitor); if (firstModule != null) { + GoPcHeader pcHeader = firstModule.getPcHeader(); + this.minLC = pcHeader.getMinLC(); + if (pcHeader.getPtrSize() != ptrSize) { + throw new IOException( + "Mismatched ptrSize: %d vs %d".formatted(pcHeader.getPtrSize(), ptrSize)); + } addModule(firstModule); } initFuncdata(); @@ -304,6 +411,53 @@ public class GoRttiMapper extends DataTypeMapper { funcdataByName.put(funcdata.getName(), funcdata); } } + + } + + /** + * Initializes golang function / method lookup info + * + * @throws IOException if error reading data + */ + public void initMethodInfoIfNeeded() throws IOException { + if (methodsByAddr.isEmpty()) { + initMethodInfo(); + } + } + + private void initMethodInfo() throws IOException { + for (GoType goType : goTypes.values()) { + goType.getMethodInfoList().forEach(this::addMethodInfo); + } + + for (GoModuledata module : modules) { + for (GoItab itab : module.getItabs()) { + itab.getMethodInfoList().forEach(this::addMethodInfo); + + // create index of interfaces that each type implements + List itabs = interfacesImplementedByType.computeIfAbsent( + itab.getType().getTypeOffset(), unused -> new ArrayList<>()); + itabs.add(itab); + } + } + } + + private void addMethodInfo(MethodInfo bm) { + List methods = + methodsByAddr.computeIfAbsent(bm.getAddress(), unused -> new ArrayList<>()); + methods.add(bm); + } + + /** + * Returns the minLC (pcquantum) value found in the pcln header structure + * @return minLC value + * @throws IOException if value has not been initialized yet + */ + public byte getMinLC() throws IOException { + if (minLC == 0) { + throw new IOException("Unknown Golang minLC value"); + } + return minLC; } /** @@ -324,11 +478,22 @@ public class GoRttiMapper extends DataTypeMapper { modules.add(module); } - public GoParamStorageAllocator getStorageAllocator() { + /** + * Returns a new param storage allocator instance. + * + * @return new {@link GoParamStorageAllocator} instance + */ + public GoParamStorageAllocator newStorageAllocator() { GoParamStorageAllocator storageAllocator = new GoParamStorageAllocator(program, goVersion); return storageAllocator; } + /** + * Returns true if the specified function uses the abi0 calling convention. + * + * @param func {@link Function} to test + * @return boolean true if function uses abi0 calling convention + */ public boolean isGolangAbi0Func(Function func) { Address funcAddr = func.getEntryPoint(); for (Symbol symbol : func.getProgram().getSymbolTable().getSymbolsAsIterator(funcAddr)) { @@ -342,20 +507,21 @@ public class GoRttiMapper extends DataTypeMapper { return false; } - public PrototypeModel getAbi0CallingConvention() { - return abi0CallingConvention; + /** + * Returns true if the specified calling convention is defined for the program. + * @param ccName calling convention name + * @return true if the specified calling convention is defined for the program + */ + public boolean hasCallingConvention(String ccName) { + return program.getFunctionManager().getCallingConvention(ccName) != null; } - public PrototypeModel getAbiInternalCallingConvention() { - return abiInternalCallingConvention; - } + @Override + public MarkupSession createMarkupSession(TaskMonitor monitor) { + UnknownProgressWrappingTaskMonitor upwtm = new UnknownProgressWrappingTaskMonitor(monitor); + upwtm.initialize(1, "Marking up Golang RTTI structures"); - public PrototypeModel getDuffzeroCallingConvention() { - return duffzeroCallingConvention; - } - - public PrototypeModel getDuffcopyCallingConvention() { - return duffcopyCallingConvention; + return super.createMarkupSession(upwtm); } /** @@ -485,6 +651,17 @@ public class GoRttiMapper extends DataTypeMapper { return goType; } + /** + * Returns a previous read and cached GoType, based on its offset. + * + * @param offset offset of the GoType + * @return GoType, or null if not previously read and cached + */ + public GoType getCachedGoType(long offset) { + GoType goType = goTypes.get(offset); + return goType; + } + /** * Returns a specialized {@link GoType} for the type that is located at the specified location. * @@ -496,17 +673,10 @@ public class GoRttiMapper extends DataTypeMapper { return getGoType(addr.getOffset()); } - public GoType getLastGoType() { - Optional> max = goTypes.entrySet() - .stream() - .max((o1, o2) -> o1.getKey().compareTo(o2.getKey())); - return max.isPresent() ? max.get().getValue() : null; - } - /** * Finds a go type by its go-type name, from the list of - * {@link #discoverGoTypes(TaskMonitor) discovered} go types. - * + * {@link #discoverGoTypes(TaskMonitor) discovered} go types. + * * @param typeName name string * @return {@link GoType}, or null if not found */ @@ -534,9 +704,8 @@ public class GoRttiMapper extends DataTypeMapper { } } catch (IOException e) { - Msg.warn(this, "Failed to get Ghidra data type from go type: %s[%x]" - .formatted(goTypeName, - goType.getStructureContext().getStructureStart())); + Msg.warn(this, "Failed to get Ghidra data type from go type: %s[%x]".formatted( + goTypeName, goType.getStructureContext().getStructureStart())); } } } @@ -551,50 +720,65 @@ public class GoRttiMapper extends DataTypeMapper { * from an earlier golang.gdt (if this binary doesn't have DWARF) * * @param gdtFile destination {@link File} to write the bootstrap types to + * @param runtimeFuncSnapshot boolean flag, if true include function definitions * @param monitor {@link TaskMonitor} * @throws IOException if error + * @throws CancelledException if cancelled */ - public void exportTypesToGDT(File gdtFile, TaskMonitor monitor) throws IOException { + public void exportTypesToGDT(File gdtFile, boolean runtimeFuncSnapshot, TaskMonitor monitor) + throws IOException, CancelledException { + + List bootstrapFuncDefs = runtimeFuncSnapshot + ? createBootstrapFuncDefs(program.getDataTypeManager(), + GoConstants.GOLANG_BOOTSTRAP_FUNCS_CATEGORYPATH, monitor) + : List.of(); List registeredStructDTs = mappingInfo.values() .stream() - .map(smi -> { - if (smi.getStructureDataType() == null) { - return null; - } - DataType existingDT = findType(smi.getStructureName(), - List.of(DWARFProgram.DWARF_ROOT_CATPATH, DWARFProgram.UNCAT_CATPATH), - programDTM); - if (existingDT == null) { - existingDT = smi.getStructureDataType(); - } - if (existingDT == null) { - Msg.warn(this, "Missing type: " + smi.getDescription()); - } - return existingDT; - }) + .map(this::structMappingInfoToDataType) .filter(Objects::nonNull) - .collect(Collectors.toList()); + .toList(); // Copy the data types into a tmp gdt, and then copy them again into the final gdt // to avoid traces of the original program name as a deleted source archive link in the // gdt data base. This method only leaves the target gdt filename + ".step1" in the db. File tmpGDTFile = new File(gdtFile.getParentFile(), gdtFile.getName() + ".step1.gdt"); - FileDataTypeManager tmpFdtm = FileDataTypeManager.createFileArchive(tmpGDTFile); + FileDataTypeManager tmpFdtm = createFileArchive(tmpGDTFile, program.getLanguage(), + program.getCompilerSpec().getCompilerSpecID(), monitor); int tx = -1; try { tx = tmpFdtm.startTransaction("Import"); tmpFdtm.addDataTypes(registeredStructDTs, DataTypeConflictHandler.DEFAULT_HANDLER, monitor); + if (runtimeFuncSnapshot) { + tmpFdtm.addDataTypes(bootstrapFuncDefs, DataTypeConflictHandler.KEEP_HANDLER, + monitor); + } moveAllDataTypesTo(tmpFdtm, DWARFProgram.DWARF_ROOT_CATPATH, GOLANG_CP); for (SourceArchive sa : tmpFdtm.getSourceArchives()) { tmpFdtm.removeSourceArchive(sa); } tmpFdtm.getRootCategory() .removeCategory(DWARFProgram.DWARF_ROOT_CATPATH.getName(), monitor); - // TODO: could also clear out any description strings on types + + // Nuke descriptions in all data types. Most likely are DWARF debug data, etc that would + // be specific to the example program. + for (Iterator it = tmpFdtm.getAllDataTypes(); it.hasNext();) { + DataType dt = it.next(); + if (dt.getDescription() != null) { + if (dt instanceof Composite && + !GoFunctionMultiReturn.isMultiReturnDataType(dt)) { + // don't nuke the generic warning in the multi-return data type + dt.setDescription(null); + } + } + if (dt instanceof FunctionDefinition funcDef) { + funcDef.setComment(null); + } + } } - catch (CancelledException | DuplicateNameException e) { + catch (CancelledException | DuplicateNameException | DataTypeDependencyException + | InvalidNameException e) { Msg.error(this, "Error when exporting types to file: %s".formatted(gdtFile), e); } finally { @@ -605,7 +789,8 @@ public class GoRttiMapper extends DataTypeMapper { tmpFdtm.save(); - FileDataTypeManager fdtm = FileDataTypeManager.createFileArchive(gdtFile); + FileDataTypeManager fdtm = createFileArchive(gdtFile, program.getLanguage(), + program.getCompilerSpec().getCompilerSpecID(), monitor); tx = -1; try { tx = fdtm.startTransaction("Import"); @@ -628,18 +813,95 @@ public class GoRttiMapper extends DataTypeMapper { fdtm.close(); tmpGDTFile.delete(); - } + private FileDataTypeManager createFileArchive(File gdtFile, Language lang, + CompilerSpecID compilerId, TaskMonitor monitor) throws IOException { + try { + FileDataTypeManager fdtm = FileDataTypeManager.createFileArchive(gdtFile); + fdtm.setProgramArchitecture(lang, compilerId, LanguageUpdateOption.CLEAR, monitor); + return fdtm; + } + catch (IOException | CancelledException | LockException | UnsupportedOperationException + | IncompatibleLanguageException e) { + throw new IOException("Failed to create file data type manager: " + gdtFile, e); + } + } + + private DataType structMappingInfoToDataType(StructureMappingInfo smi) { + if (smi.getStructureDataType() == null) { + return null; + } + DataType existingDT = findType(smi.getStructureName(), + List.of(DWARFProgram.DWARF_ROOT_CATPATH, DWARFProgram.UNCAT_CATPATH), programDTM); + if (existingDT == null) { + existingDT = smi.getStructureDataType(); + } + if (existingDT == null) { + Msg.warn(this, "Missing type: " + smi.getDescription()); + } + return existingDT; + } + + + private List createBootstrapFuncDefs(DataTypeManager destDTM, CategoryPath destCP, + TaskMonitor monitor) throws CancelledException { + List funcs = getAllFunctions().stream() + .filter(funcData -> funcData.getFlags().isEmpty()) + .map(BootstrapFuncInfo::from) + .filter(Objects::nonNull) +// .filter(funcInfo -> !GoFunctionMultiReturn +// .isMultiReturnDataType(funcInfo.func.getReturnType())) + .filter(BootstrapFuncInfo::isBootstrapFunction) + .filter(BootstrapFuncInfo::isNotPlatformSpecificSourceFile) + .map(BootstrapFuncInfo::func) + .toList(); + monitor.initialize(funcs.size(), "Creating golang bootstrap function defs"); + List results = new ArrayList<>(); + for (Function func : funcs) { + monitor.increment(); + try { + FunctionDefinitionDataType funcDef = + new FunctionDefinitionDataType(func.getSignature()); + funcDef.setCategoryPath(destCP); + funcDef.setCallingConvention(null); + funcDef.setComment(null); + DataType newDT = destDTM.addDataType(funcDef, DataTypeConflictHandler.KEEP_HANDLER); + results.add(newDT); + } + catch (InvalidInputException e) { + // skip + } + } + return results; + } + + private void moveAllDataTypesTo(DataTypeManager dtm, CategoryPath srcCP, CategoryPath destCP) - throws DuplicateNameException { + throws DuplicateNameException, DataTypeDependencyException, InvalidNameException { Category srcCat = dtm.getCategory(srcCP); if (srcCat != null) { for (DataType dt : srcCat.getDataTypes()) { if (dt instanceof Array || dt instanceof Pointer) { continue; } - dt.setCategoryPath(destCP); + String destName = dt.getName(); + if (dt instanceof TypeDef td && td.getName().startsWith(".param")) { + // special case of typedefs called ".paramNNN" (created by go for generic type params) + // The type name needs to be tweaked to prevent clashes with others + destName = td.getCategoryPath().getName() + dt.getName(); + } + DataType existingDT = dtm.getDataType(new DataTypePath(destCP, destName)); + if (existingDT != null) { + if (DWARFDataTypeConflictHandler.INSTANCE.resolveConflict(dt, + existingDT) != ConflictResult.USE_EXISTING) { + throw new DuplicateNameException("Error moving golang type: [%s] to [%s]" + .formatted(dt.getDataTypePath(), existingDT.getDataTypePath())); + } + dtm.replaceDataType(dt, existingDT, false); + continue; + } + dt.setNameAndCategory(destCP, destName); } for (Category subcat : srcCat.getCategories()) { moveAllDataTypesTo(dtm, subcat.getCategoryPath(), destCP); // flatten everything @@ -649,11 +911,16 @@ public class GoRttiMapper extends DataTypeMapper { /** * Returns category path that should be used to place recovered golang types. - * + + * @param packagePath optional package path of the type (eg. "utf/utf8", or "runtime") * @return {@link CategoryPath} to use when creating recovered golang types */ - public CategoryPath getRecoveredTypesCp() { - return RECOVERED_TYPES_CP; + public CategoryPath getRecoveredTypesCp(String packagePath) { + CategoryPath result = RECOVERED_TYPES_CP; + if (packagePath != null && !packagePath.isEmpty()) { + result = result.extend(packagePath); + } + return result; } /** @@ -679,6 +946,48 @@ public class GoRttiMapper extends DataTypeMapper { return dt; } + /** + * Returns a function definition for a method that is attached to a golang type. + *

    + * + * @param methodName name of method + * @param methodType golang function def type + * @param receiverDT data type of the go type that contains the method + * @param allowPartial boolean flag, if true allows returning an artificial funcdef when the + * methodType parameter does not point to a function definition + * @return new {@link FunctionDefinition} using the function signature specified by the + * methodType function definition, with the containing goType's type inserted as the first + * parameter, similar to a c++ "this" parameter + * @throws IOException if error reading type info + */ + public FunctionDefinition getSpecializedMethodSignature(String methodName, GoType methodType, + DataType receiverDT, boolean allowPartial) throws IOException { + if ((methodType == null && !allowPartial) || receiverDT == null) { + return null; + } + FunctionDefinition methodFuncDef = methodType != null + ? GoFuncType.unwrapFunctionDefinitionPtrs(getRecoveredType(methodType)) + : new FunctionDefinitionDataType("empty", program.getDataTypeManager()); + if (methodFuncDef == null) { + return null; + } + methodFuncDef = (FunctionDefinition) methodFuncDef.copy(program.getDataTypeManager()); + try { + methodFuncDef.setNameAndCategory(receiverDT.getCategoryPath(), methodName); + List args = + new ArrayList<>(Arrays.asList(methodFuncDef.getArguments())); + args.add(0, new ParameterDefinitionImpl(null, receiverDT, null)); + methodFuncDef.setArguments(args.toArray(ParameterDefinition[]::new)); + + return methodFuncDef; + } + catch (InvalidNameException | DuplicateNameException e) { + Msg.warn(this, "Error when creating function signature for method", e); + return null; + } + + } + /** * Inserts a mapping between a {@link GoType golang type} and a * {@link DataType ghidra data type}. @@ -725,12 +1034,10 @@ public class GoRttiMapper extends DataTypeMapper { * @throws CancelledException if the user cancelled the import */ public void recoverDataTypes(TaskMonitor monitor) throws IOException, CancelledException { - monitor.setMessage("Converting Golang types to Ghidra data types"); - monitor.initialize(goTypes.size()); - List typeOffsets = goTypes.keySet().stream().sorted().collect(Collectors.toList()); + monitor.initialize(goTypes.size(), "Converting Golang types to Ghidra data types"); + List typeOffsets = goTypes.keySet().stream().sorted().toList(); for (Long typeOffset : typeOffsets) { - monitor.checkCancelled(); - monitor.incrementProgress(1); + monitor.increment(); GoType typ = getGoType(typeOffset); DataType dt = typ.recoverDataType(); if (programDTM.getDataType(dt.getDataTypePath()) == null) { @@ -739,6 +1046,19 @@ public class GoRttiMapper extends DataTypeMapper { } } + /** + * Discovers available golang types if not already done. + * + * @param monitor {@link TaskMonitor} + * @throws CancelledException if cancelled + * @throws IOException if error reading data + */ + public void initTypeInfoIfNeeded(TaskMonitor monitor) throws CancelledException, IOException { + if (goTypes.isEmpty()) { + discoverGoTypes(monitor); + } + } + /** * Iterates over all golang rtti types listed in the GoModuledata struct, and recurses into * each type to discover any types they reference. @@ -750,10 +1070,8 @@ public class GoRttiMapper extends DataTypeMapper { * @throws CancelledException if cancelled */ public void discoverGoTypes(TaskMonitor monitor) throws IOException, CancelledException { - UnknownProgressWrappingTaskMonitor upwtm = - new UnknownProgressWrappingTaskMonitor(monitor, 50); + UnknownProgressWrappingTaskMonitor upwtm = new UnknownProgressWrappingTaskMonitor(monitor); upwtm.setMessage("Iterating Golang RTTI types"); - upwtm.initialize(0); goTypes.clear(); typeNameIndex.clear(); @@ -767,13 +1085,135 @@ public class GoRttiMapper extends DataTypeMapper { type.discoverGoTypes(discoveredTypes); } } + + // Fix non-unique type names, which can happen when types are embedded inside a function, + // (example: func foo() { type result;... }, in dir1/packageA and in dir2/packageA) + Map typeDupCount = new HashMap<>(); for (GoType goType : goTypes.values()) { - String typeName = goType.getNameString(); - typeNameIndex.put(typeName, goType); + String typeName = goType.getNameWithPackageString(); + typeDupCount.merge(typeName, 1, (i1, i2) -> i1 + i2); + } + Set dupedTypeNames = typeDupCount.entrySet() + .stream() + .filter(entry -> entry.getValue() > 1) + .map(Entry::getKey) + .collect(toSet()); + typeDupCount.clear(); + + for (GoType goType : goTypes.values()) { + String typeName = goType.getNameWithPackageString(); + if (dupedTypeNames.contains(typeName)) { + typeName = typeName + "." + typeDupCount.merge(typeName, 1, (i1, i2) -> i1 + i2); + } + GoType existingType = typeNameIndex.put(typeName, goType); + if (existingType != null) { + Msg.warn(this, "Go type name conflict: " + typeName); + } + fixedGoTypeNames.put(goType.getTypeOffset(), typeName); } Msg.info(this, "Found %d golang types".formatted(goTypes.size())); + + // these structure types are what golang map and chan types actually point to. + mapGoType = findGoType("runtime.hmap"); + chanGoType = findGoType("runtime.hchan"); } + /** + * Returns a list of methods (either gotype methods or interface methods) that point + * to this function. + * + * @param funcAddr function address + * @return list of methods + */ + public List getMethodInfoForFunction(Address funcAddr) { + List result = methodsByAddr.get(funcAddr); + return result != null ? result : List.of(); + } + + /** + * Returns a list of interfaces that the specified type has implemented. + * + * @param type GoType + * @return list of itabs that map a GoType to the interfaces it was found to implement + */ + public List getInterfacesImplementedByType(GoType type) { + return interfacesImplementedByType.getOrDefault(type.getTypeOffset(), List.of()); + } + + /** + * Returns a unique name for the specified go type. + * @param goType {@link GoType} + * @return unique string name + */ + public String getUniqueGoTypename(GoType goType) { + String name = fixedGoTypeNames.get(goType.getTypeOffset()); + if (name == null) { + name = goType.getNameWithPackageString(); + } + return name; + } + + public interface GoNameSupplier { + GoName get() throws IOException; + } + + /** + * An exception handling wrapper around a "getName()" call that could throw an IOException. + *

    + * When there is an error fetching the GoName instance via the specified callback, a limited + * usage GoName instance will be created and returned that will provide a replacement name + * that is built using the calling structure's offset as the identifier. + * + * @param struct mapped instance type + * @param supplier Supplier callback + * @param structInstance reference to the caller's struct-mapped instance + * @param defaultValue string value to return (wrapped in a GoName) if the GoName is simply + * missing + * @return GoName, either from the callback, or a limited-functionality instance created to + * hold a fallback name string + */ + public GoName getSafeName(GoNameSupplier supplier, T structInstance, String defaultValue) { + try { + GoName result = supplier.get(); + if (result != null) { + return result; + } + // fall thru, return a fake GoName with defaultValue + } + catch (IOException e) { + // fall thru, return fallback name, but ensure defaultValue isn't used + defaultValue = null; + } + + StructureContext structContext = getStructureContextOfInstance(structInstance); + String fallbackName = defaultValue; + fallbackName = fallbackName == null && structContext != null + ? "%s_%x".formatted(structContext.getMappingInfo().getStructureName(), + structContext.getStructureStart()) + : "invalid_object"; + return GoName.createFakeInstance(fallbackName); + } + + /** + * Returns the name of a gotype. + * + * @param offset offset of the gotype RTTI record + * @return string name, with a fallback if the specified offset was invalid + */ + public String getGoTypeName(long offset) { + try { + GoType goType = getGoType(offset); + if (goType != null) { + return goType.getName(); + } + } + catch (IOException e) { + // fall thru + } + return "unknown_type_%x".formatted(offset); + } + + /** * Returns the {@link GoType} corresponding to an offset that is relative to the controlling * GoModuledata's typesOffset. @@ -847,24 +1287,48 @@ public class GoRttiMapper extends DataTypeMapper { return offset != 0 ? readStructure(GoName.class, offset) : null; } - public GoFuncData getFunctionData(Address funcAddr) throws IOException { + /** + * Returns metadata about a function + * + * @param funcAddr entry point of a function + * @return {@link GoFuncData}, or null if function not found in lookup tables + */ + public GoFuncData getFunctionData(Address funcAddr) { return funcdataByAddr.get(funcAddr); } + /** + * Returns a function based on its name + * + * @param funcName name of function + * @return {@link GoFuncData}, or null if not found + */ public GoFuncData getFunctionByName(String funcName) { return funcdataByName.get(funcName); } - public List getAllFunctions() throws IOException { + /** + * Return a list of all functions + * + * @return list of all functions contained in the golang func metadata table + */ + public List getAllFunctions() { return new ArrayList<>(funcdataByAddr.values()); } - //-------------------------------------------------------------------------------------------- - - private void initHiddenCompilerTypes() { - // these structure types are what golang map and chan types actually point to. - mapGoType = findGoType("runtime.hmap"); - chanGoType = findGoType("runtime.hchan"); + /** + * Returns a {@link FunctionDefinition} for a built-in golang runtime function. + * + * @param funcName name of function + * @return {@link FunctionDefinition}, or null if not found in bootstrap gdt + */ + public FunctionDefinition getBootstrapFunctionDefintion(String funcName) { + if (archiveDTM != null) { + DataType dt = + archiveDTM.getDataType(GoConstants.GOLANG_BOOTSTRAP_FUNCS_CATEGORYPATH, funcName); + return dt instanceof FunctionDefinition funcDef ? funcDef : null; + } + return null; } private GoModuledata findFirstModuledata(TaskMonitor monitor) throws IOException { @@ -893,24 +1357,129 @@ public class GoRttiMapper extends DataTypeMapper { } private AddressRange getPclntabSearchRange() { + MemoryBlock memBlock = getFirstMemoryBlock(program, ".noptrdata", ".rdata"); + return memBlock != null + ? new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd()) + : null; + } + + private AddressRange getModuledataSearchRange() { + MemoryBlock memBlock = getFirstMemoryBlock(program, ".noptrdata", ".data"); + return memBlock != null + ? new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd()) + : null; + } + + /** + * Returns the address range that is valid for string structs to be found in. + * + * @return {@link AddressSetView} of range that is valid to find string structs in + */ + public AddressSetView getStringStructRange() { + MemoryBlock datamb = program.getMemory().getBlock(".data"); + MemoryBlock rodatamb = getFirstMemoryBlock(program, ".rodata", ".rdata"); + + if (datamb == null || rodatamb == null) { + return new AddressSet(); + } + + AddressSet result = new AddressSet(datamb.getStart(), datamb.getEnd()); + result.add(rodatamb.getStart(), rodatamb.getEnd()); + return result; + } + + /** + * Returns the address range that is valid for string char[] data to be found in. + * + * @return {@link AddressSetView} of range that is valid for string char[] data + */ + public AddressSetView getStringDataRange() { + MemoryBlock rodatamb = getFirstMemoryBlock(program, ".rodata", ".rdata"); + return rodatamb != null + ? new AddressSet(rodatamb.getStart(), rodatamb.getEnd()) + : new AddressSet(); + } + + private static MemoryBlock getFirstMemoryBlock(Program program, String... blockNames) { Memory memory = program.getMemory(); - for (String blockToSearch : List.of(".noptrdata", ".rdata")) { + for (String blockToSearch : blockNames) { MemoryBlock memBlock = memory.getBlock(blockToSearch); if (memBlock != null) { - return new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd()); + return memBlock; } } return null; } - private AddressRange getModuledataSearchRange() { - Memory memory = program.getMemory(); - for (String blockToSearch : List.of(".noptrdata", ".data")) { - MemoryBlock memBlock = memory.getBlock(blockToSearch); - if (memBlock != null) { - return new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd()); + //-------------------------------------------------------------------------------------------- + record BootstrapFuncInfo(GoFuncData funcData, Function func) { + /** + * Golang package paths that will be used to decide if a function qualifies as a bootstrap + * function and be included in the golang.gdt file. + */ + private static final Set BOOTSTRAP_PACKAGE_PATHS = Set.of("archive/tar", + "archive/zip", "bufio", "bytes", "compress/bzip2", "compress/flate", "compress/gzip", + "compress/lzw", "compress/zlib", "container/heap", "container/list", "container/ring", + "context", "crypto", "crypto/aes", "crypto/cipher", "crypto/des", "crypto/dsa", + "crypto/ecdh", "crypto/ecdsa", "crypto/ed25519", "crypto/elliptic", "crypto/hmac", + "crypto/md5", "crypto/rand", "crypto/rc4", "crypto/rsa", "crypto/sha1", "crypto/sha256", + "crypto/sha512", "crypto/subtle", "crypto/tls", "crypto/x509", "database/sql", + "debug/buildinfo", "debug/elf", "debug/gosym", "errors", "flag", "fmt", "io", "io/fs", + "io/ioutil", "log", "log/syslog", "math", "math/big", "math/rand", "net", "net/http", + "net/url", "os", "path", "path/filepath", "plugin", "runtime", "sort", "strconv", + "strings", "sync", "text/scanner", "text/tabwriter", "text/template", "time", "unicode", + "unicode/utf16", "unicode/utf8", "unsafe", + + "reflect", "internal/cpu", "internal/fmtsort", "internal/reflectlite"); + + /** + * Source filename exclusion filters, matched against a function's source filename. + */ + private static final Set BOOTSTRAP_SRCFILE_PLATFORM_SPECIFIC = + Set.of("amd64", "arm" /*arm, arm64*/, "loong64", "ppc64", "386", "mips" /*mips,mips64*/, + "risc", "s390", "wasm", "aix", "android", "darwin", "dragonfly", "freebsd", "linux", + "windows", "openbsd", "ios"); + + static BootstrapFuncInfo from(GoFuncData funcData) { + Function func = funcData.getFunction(); + return func != null ? new BootstrapFuncInfo(funcData, func) : null; + } + + /** + * Returns true if the specified function should be included in the bootstrap function defs + * that are written to the golang_NNNN.gdt archive. + *

    + * @return true if function should be included in golang.gdt bootstrap file + */ + public boolean isBootstrapFunction() { + String packagePath = funcData.getSymbolName().getPackagePath(); + return packagePath != null && BOOTSTRAP_PACKAGE_PATHS.contains(packagePath); + } + + /** + * Returns true if function is a generic function and is not located in a source filename + * that has a platform specific substring (eg. "_linux") + * + * @return true if function is a generic function and is not located in a platform specific + * source file + */ + public boolean isNotPlatformSpecificSourceFile() { + try { + GoSourceFileInfo sfi = funcData.getSourceFileInfo(); + String sourceFilename = sfi != null ? sfi.getFileName() : null; + if (sourceFilename != null) { + for (String sourceFileExclude : BOOTSTRAP_SRCFILE_PLATFORM_SPECIFIC) { + if (sourceFilename.contains("_" + sourceFileExclude)) { + return false; + } + } + } + return true; + } + catch (IOException e) { + return false; } } - return null; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSlice.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSlice.java index 680b34a8aa..1b67f90bd0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSlice.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSlice.java @@ -21,14 +21,26 @@ import java.util.List; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.BinaryReader.ReaderFunction; +import ghidra.app.util.bin.format.golang.rtti.types.GoSliceType; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.mem.Memory; import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +/** + * A structure that represents a golang slice instance (similar to a java ArrayList). Not to be + * confused with a {@link GoSliceType}, which is RTTI info about a slice type. + *

    + * An initialized static image of a slice found in a go binary will tend to have len==cap (full). + *

    + * Like java's type erasure for generics, a golang slice instance does not have type information + * about the elements found in the array blob (nor the size of the blob). + *

    + */ @StructureMapping(structureName = "runtime.slice") -public class GoSlice { +public class GoSlice implements StructureMarkup { @ContextField private GoRttiMapper programContext; @@ -37,20 +49,24 @@ public class GoSlice { private StructureContext context; @FieldMapping - private long array; + private long array; // pointer to data @FieldMapping - private long len; + private long len; // number of active elements @FieldMapping - private long cap; + private long cap; // number of elements that can be stored in the array public GoSlice() { // emtpy } - public GoSlice(long array, long len, long cap) { - this(array, len, cap, null); - } - + /** + * Creates an artificial slice instance using the supplied values. + * + * @param array offset of the slice's data + * @param len number of initialized elements in the slice + * @param cap total number of elements in the data array + * @param programContext the go binary that contains the slice + */ public GoSlice(long array, long len, long cap, GoRttiMapper programContext) { this.array = array; this.len = len; @@ -58,6 +74,27 @@ public class GoSlice { this.programContext = programContext; } + /** + * Returns the {@link DataType} of elements of this slice, as detected by the type information + * contained in the struct field that contains this slice. + *

    + * Returns null if this slice instance was not nested (contained) in a structure. If the + * slice data type wasn't a specialized slice data type (it was "runtime.slice" instead of + * "[]element"), void data type will be returned. + * + * @return data type of the elements of this slice, if possible, or null + */ + public DataType getElementDataType() { + DataType dt = context != null ? context.getContainingFieldDataType() : null; + if (dt != null && dt instanceof Structure struct && struct.getNumDefinedComponents() > 0) { + int elementPtrFieldOrdinal = 0; // hacky hard coded knowledge that the pointer to the data is the first element of the slice struct + DataTypeComponent elementPtrDTC = struct.getComponent(elementPtrFieldOrdinal); + DataType elementPtrDT = elementPtrDTC.getDataType(); + return elementPtrDT instanceof Pointer ptrDT ? ptrDT.getDataType() : null; + } + return null; + } + /** * Return a artificial view of a portion of this slice's contents. * @@ -67,13 +104,25 @@ public class GoSlice { * @return new {@link GoSlice} instance that is limited to a portion of this slice */ public GoSlice getSubSlice(long startElement, long elementCount, long elementSize) { - return new GoSlice(array + (startElement * elementSize), elementCount, elementCount, programContext); + return new GoSlice(array + (startElement * elementSize), elementCount, elementCount, + programContext); } + /** + * Returns true if this slice seems valid. + * + * @return boolean true if array blob is a valid memory location + */ public boolean isValid() { return array != 0 && isValid(1); } + /** + * Returns true if this slice seems valid. + * + * @param elementSize size of elements in this slice + * @return boolean true if array blob is a valid memory location + */ public boolean isValid(int elementSize) { try { Memory memory = programContext.getProgram().getMemory(); @@ -86,35 +135,83 @@ public class GoSlice { } } + /** + * Returns address of the array blob. + * + * @return location of the array blob + */ public long getArrayOffset() { return array; } + /** + * Returns the address of the array blob + * @return address of the array blob + */ public Address getArrayAddress() { return programContext.getDataAddress(array); } + /** + * Returns the address of the end of the array. + * + * @param elementClass structure mapped class + * @return location of the end of the array blob + */ public long getArrayEnd(Class elementClass) { StructureMappingInfo elementSMI = context.getDataTypeMapper().getStructureMappingInfo(elementClass); - int elementLength = elementSMI.getStructureLength(); - return array + len * elementLength; + return getElementOffset(elementSMI.getStructureLength(), len); } + /** + * Returns the number of initialized elements + * + * @return number of initialized elements + */ public long getLen() { return len; } + /** + * Returns the number of elements allocated in the array blob. (capacity) + * + * @return number of allocated elements in the array blob + */ public long getCap() { return cap; } + /** + * Returns true if this slice's element count is equal to the slice's capacity. This is + * typically true for all slices that are static. + * + * @return boolean true if this slice's element count is equal to capacity + */ public boolean isFull() { return len == cap; } - public boolean isOffsetWithinData(long offset, int sizeofElement) { - return array <= offset && offset < array + (cap * sizeofElement); + /** + * Returns true if this slice contains the specified offset. + * + * @param offset memory offset in question + * @param sizeofElement size of elements in this slice + * @return true if this slice contains the specified offset + */ + public boolean containsOffset(long offset, int sizeofElement) { + return array <= offset && offset < getElementOffset(sizeofElement, cap); + } + + /** + * Returns the offset of the specified element + * + * @param elementSize size of elements in this slice + * @param elementIndex index of desired element + * @return offset of element + */ + public long getElementOffset(long elementSize, long elementIndex) { + return array + elementSize * elementIndex; } /** @@ -152,7 +249,8 @@ public class GoSlice { } else { // ensure that the reader func is doing correct thing - if (elementSize > 0 && reader.getPointerIndex() != array + (i + 1) * elementSize) { + if (elementSize > 0 && + reader.getPointerIndex() != getElementOffset(elementSize, i + 1)) { Msg.warn(this, "Bad element size when reading slice element (size: %d) at %d" .formatted(elementSize, reader.getPointerIndex())); elementSize = 0; @@ -174,38 +272,65 @@ public class GoSlice { return readUIntList(reader, array, intSize, (int) len); } + /** + * Reads an unsigned int element from this slice. + * + * @param intSize size of ints + * @param elementIndex index of element + * @return unsigned int value + * @throws IOException if error reading element + */ + public long readUIntElement(int intSize, int elementIndex) throws IOException { + return getElementReader(intSize, elementIndex).readNextUnsignedValue(intSize); + } + + /** + * Returns a {@link BinaryReader} positioned at the specified slice element. + * + * @param elementSize size of elements in this slice + * @param elementIndex index of desired element + * @return {@link BinaryReader} positioned at specified element + */ + public BinaryReader getElementReader(int elementSize, int elementIndex) { + BinaryReader reader = programContext.getReader(getElementOffset(elementSize, elementIndex)); + return reader; + } + /** * Marks up the memory occupied by the array elements with a name and a Ghidra ArrayDataType, * which has elements who's type is determined by the specified structure class. * * @param sliceName used to label the memory location + * @param namespaceName namespace the label symbol should be placed in * @param elementClazz structure mapped class of the element of the array * @param ptr boolean flag, if true the element type is really a pointer to the supplied * data type * @param session state and methods to assist marking up the program * @throws IOException if error */ - public void markupArray(String sliceName, Class elementClazz, boolean ptr, - MarkupSession session) throws IOException { + public void markupArray(String sliceName, String namespaceName, Class elementClazz, + boolean ptr, MarkupSession session) throws IOException { DataType dt = programContext.getStructureDataType(elementClazz); - markupArray(sliceName, dt, ptr, session); + markupArray(sliceName, namespaceName, dt, ptr, session); } /** * Marks up the memory occupied by the array elements with a name and a Ghidra ArrayDataType. * * @param sliceName used to label the memory location + * @param namespaceName namespace the label symbol should be placed in * @param elementType Ghidra datatype of the array elements, null ok if ptr == true * @param ptr boolean flag, if true the element type is really a pointer to the supplied * data type * @param session state and methods to assist marking up the program * @throws IOException if error */ - public void markupArray(String sliceName, DataType elementType, boolean ptr, - MarkupSession session) throws IOException { + public void markupArray(String sliceName, String namespaceName, DataType elementType, + boolean ptr, MarkupSession session) throws IOException { if (len == 0 || !isValid()) { return; } + DataTypeManager dtm = programContext.getDTM(); if (ptr) { elementType = new PointerDataType(elementType, programContext.getPtrSize(), dtm); @@ -215,7 +340,7 @@ public class GoSlice { Address addr = programContext.getDataAddress(array); session.markupAddress(addr, arrayDT); if (sliceName != null) { - session.labelAddress(addr, sliceName); + session.labelAddress(addr, sliceName, namespaceName); } } @@ -227,9 +352,10 @@ public class GoSlice { * @param session state and methods to assist marking up the program * @return list of element instances * @throws IOException if error reading + * @throws CancelledException if cancelled */ public List markupArrayElements(Class clazz, MarkupSession session) - throws IOException { + throws IOException, CancelledException { if (len == 0) { return List.of(); } @@ -272,4 +398,13 @@ public class GoSlice { return result; } + @Override + public String getStructureLabel() throws IOException { + return "slice[%d]_%s".formatted(len, context.getStructureAddress()); + } + + @Override + public StructureContext getStructureContext() { + return context; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSourceFileInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSourceFileInfo.java new file mode 100644 index 0000000000..bdbadaf109 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSourceFileInfo.java @@ -0,0 +1,57 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.util.bin.format.golang.rtti; + +/** + * Represents a golang source file and line number tuple. + * + * @param fileName source filename + * @param lineNum source line number + */ +public record GoSourceFileInfo(String fileName, int lineNum) { + + public GoSourceFileInfo(String fileName, int lineNum) { + this.fileName = fileName; + this.lineNum = lineNum; + } + + public String getFileName() { + return fileName; + } + + public int getLineNum() { + return lineNum; + } + + /** + * Returns the source location info as a string formatted as "filename:linenum" + * + * @return "filename:linenum" + */ + public String getDescription() { + return "%s:%d".formatted(fileName, lineNum); + } + + /** + * Returns the source location info as a string formatted as "File: filename Line: linenum" + * + * @return "File: filename Line: linenum" + */ + public String getVerboseDescription() { + return "File: %s Line: %d".formatted(fileName, lineNum); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoString.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoString.java index 89079de6da..162144b3d7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoString.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoString.java @@ -16,35 +16,221 @@ package ghidra.app.util.bin.format.golang.rtti; import java.io.IOException; +import java.util.function.Predicate; import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf4.next.DWARFDataInstanceHelper; import ghidra.app.util.bin.format.golang.structmapping.*; -import ghidra.program.model.address.Address; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +/** + * A structure that represents a golang string instance. + */ @StructureMapping(structureName = "string") -public class GoString { +public class GoString implements StructureMarkup { + public static final int MAX_SANE_STR_LEN = 1024 * 1024; // 1mb + + /** + * Creates a artificial gostring instance that was not read from a memory location. + *

    + * @param goBinary {@link GoRttiMapper} + * @param stringData location of char array + * @param len length of char array + * @return new GoString instance + */ + public static GoString createInlineString(GoRttiMapper goBinary, Address stringData, long len) { + GoString result = new GoString(); + result.context = goBinary.createArtificialStructureContext(GoString.class); + result.str = stringData.getOffset(); + result.len = len; + return result; + } @ContextField private StructureContext context; @FieldMapping - @MarkupReference("stringAddr") - @EOLComment("stringValue") + @MarkupReference("getStringAddr") + @EOLComment("getStringValue") private long str; @FieldMapping private long len; + /** + * Returns the address of the char data, referenced via the str field's markup annotation + * @return address of the char data + */ public Address getStringAddr() { return context.getDataTypeMapper().getDataAddress(str); } + /** + * Returns an AddressRange that encompasses the string char data. + * + * @return AddressRange that encompasses the string char data + */ + public AddressRange getStringDataRange() { + if (len <= 0) { + return null; + } + Address charStart = context.getDataTypeMapper().getDataAddress(str); + Address charEnd = context.getDataTypeMapper().getDataAddress(str + len - 1); + return new AddressRangeImpl(charStart, charEnd); + } + + /** + * Returns the length of the string data + * + * @return length of the string data + */ public long getLength() { return len; } + /** + * Returns the string value. + * + * @return string value + * @throws IOException if error reading char data + */ public String getStringValue() throws IOException { BinaryReader reader = context.getDataTypeMapper().getReader(str); return reader.readNextUtf8String((int) len); } + + private DataType getStringCharDataType() { + // use char as the element data type because it renders better, even though + // it causes a type-cast to uint8* in the decompiler + return new ArrayDataType(CharDataType.dataType, (int) len, -1, + context.getDataTypeMapper().getDTM()); + } + + /** + * Returns true if this string instance is valid and probably contains a go string. + * + * @param charValidRange addresses that are valid locations for a string's char[] data + * @param stringContentValidator a callback that will test a recovered string for validity + * @return boolean true if valid string, false if not valid string + * @throws IOException if error reading data + */ + public boolean isValid(AddressSetView charValidRange, Predicate stringContentValidator) + throws IOException { + + if (len <= 0 || len > MAX_SANE_STR_LEN) { + return false; + } + + Address structStartAddr = context.getStructureAddress(); + AddressRange charDataRange = getStringDataRange(); + if (charDataRange == null || !charValidRange.contains(charDataRange.getMinAddress(), + charDataRange.getMaxAddress())) { + return false; + } + + DWARFDataInstanceHelper dihUtil = + new DWARFDataInstanceHelper(context.getDataTypeMapper().getProgram()); + if (!dihUtil.isDataTypeCompatibleWithAddress(context.getStructureDataType(), + structStartAddr)) { + return false; + } + + long maxValidLen = + charValidRange.getMaxAddress().subtract(charDataRange.getMinAddress()) - 1; + if (len > maxValidLen) { + return false; + } + if (!isCompatibleCharDataType(charDataRange.getMinAddress())) { + return false; + } + if (hasOffcutReferences(charDataRange)) { + return false; + } + + String stringValue = getStringValue(); + if (!stringContentValidator.test(stringValue)) { + return false; + } + + return true; + } + + private boolean hasOffcutReferences(AddressRange charDataRange) { + AddressIterator it = context.getDataTypeMapper() + .getProgram() + .getReferenceManager() + .getReferenceDestinationIterator(new AddressSet(charDataRange), true); + Address refAddr = it.hasNext() ? it.next() : null; + if (refAddr != null && refAddr.equals(charDataRange.getMinAddress())) { + refAddr = it.hasNext() ? it.next() : null; + } + return refAddr != null; + } + + private boolean isCompatibleCharDataType(Address charDataAddr) { + DataType stringCharDataType = getStringCharDataType(); + DWARFDataInstanceHelper dihUtil = + new DWARFDataInstanceHelper(context.getDataTypeMapper().getProgram()); + return dihUtil.isDataTypeCompatibleWithAddress(stringCharDataType, charDataAddr); + } + + /** + * Returns true if this string instance points to valid char[] data. + * + * @param charValidRange addresses that are valid locations for a string's char[] data + * @param stringContentValidator a callback that will test a recovered string for validity + * @return boolean true if valid string, false if not valid string + * @throws IOException if error reading data + */ + public boolean isValidInlineString(AddressSetView charValidRange, + Predicate stringContentValidator) throws IOException { + if (len <= 0 || len > MAX_SANE_STR_LEN) { + return false; + } + + Address charStart = getStringAddr(); + + try { + Address charEnd = charStart.addNoWrap(len - 1); + if (!charValidRange.contains(charStart) || !charValidRange.contains(charEnd)) { + // TODO: maybe change check to ensure both ends of char array are in same contiguous block + return false; + } + } + catch (AddressOverflowException e) { + return false; + } + + long maxValidLen = charValidRange.getMaxAddress().subtract(charStart) - 1; + if (len > maxValidLen) { + return false; + } + if (!isCompatibleCharDataType(charStart)) { + return false; + } + + String stringValue = getStringValue(); + if (!stringContentValidator.test(stringValue)) { + return false; + } + + return true; + } + + @Override + public String getStructureLabel() throws IOException { + return StringDataInstance.makeStringLabel("gostr_", getStringValue(), + DataTypeDisplayOptions.DEFAULT); + } + + @Override + public StructureContext getStructureContext() { + return context; + } + + @Override + public void additionalMarkup(MarkupSession session) throws IOException { + session.markupAddress(getStringAddr(), getStringCharDataType()); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSymbolName.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSymbolName.java new file mode 100644 index 0000000000..7539ca1e7b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSymbolName.java @@ -0,0 +1,207 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.util.bin.format.golang.rtti; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ghidra.program.database.symbol.FunctionSymbol; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.*; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.InvalidInputException; + +/** + * Represents a Golang symbol name. + * + * @param symbolName full name of the golang symbol + * @param packagePath portion the symbol name that is the packagePath (path+packagename), or null + * @param packageName portion of the symbol name that is the package name, or null + * @param receiverString portion of the symbol name that is the receiver string (only found when + * the receiver is in the form of "(*typename)"), or null + */ +public record GoSymbolName(String symbolName, String packagePath, String packageName, + String receiverString) { + + /** + * Fixes the specified string if it contains any of the golang special symbolname characters: + * middle-dot and the weird slash. + * + * @param s string to fix + * @return original string, or fixed version + */ + public static String fixGolangSpecialSymbolnameChars(String s) { + // "\u00B7" -> "." + // "\u2215" -> "/" + if (s.contains("\u00B7") || s.contains("\u2215")) { + s = s.replaceAll("\u00B7", ".").replaceAll("\u2215", "/"); + } + return s; + } + + /** + * Matches golang symbol strings such as: + * "package/domain.name/packagename.(*ReceiverTypeName).Functionname" + * or + * "type:.eq.[39]package/domain.name/packagename.Functionname" + */ + private static final Pattern SYMBOL_INFO_PATTERN = Pattern.compile( + // "type:" ".eq." or ".hash.", optional slice "[numbers_or_dots]", + "^(type:\\.(eq|hash)\\.(\\[[0-9.]*\\])?)?" + + // package_path/package_name.(*optional_receiverstring)remainder_of_symbol_string + "(([-+_/.a-zA-Z0-9]+)(\\(\\*[^)]+\\))?.*)"); + + /** + * Parses a golang symbol string and returns a GoSymbolName instance. + * + * @param s string to parse + * @return new GoSymbolName instance, never null + */ + public static GoSymbolName parse(String s) { + + s = fixGolangSpecialSymbolnameChars(s); + + Matcher m = SYMBOL_INFO_PATTERN.matcher(s); + if (s.startsWith("go:") || !m.matches()) { + return new GoSymbolName(s); + } + + String packageString = m.group(5); + String receiverString = m.group(6); + + int packageNameStart = packageString.lastIndexOf('/') + 1; + int firstDot = packageString.indexOf('.', packageNameStart); + if (firstDot <= 0) { + return new GoSymbolName(s); + } + String packagePath = packageString.substring(0, firstDot); + String packageName = packageString.substring(packageNameStart, firstDot); + if (receiverString != null && !receiverString.isEmpty()) { + receiverString = receiverString.substring(1, receiverString.length() - 1); + } + return new GoSymbolName(s, packagePath, packageName, receiverString); + } + + /** + * Constructs a minimal GoSymbolName instance from the supplied values. + * + * @param packageName package name, does not handle package paths, eg. "runtime" + * @param symbolName full symbol name, eg. "runtime.foo" + * @return new GoSymbolName instance + */ + public static GoSymbolName from(String packageName, String symbolName) { + return new GoSymbolName(symbolName, packageName, packageName, null); + } + + /** + * Constructs a GoSymbolName instance that only has a package path / package name. + * + * @param packagePath package path to parse + * @return GoSymbolName that only has a package path and package name value + */ + public static GoSymbolName fromPackagePath(String packagePath) { + GoSymbolName tmp = parse(packagePath + ".TMP"); + return new GoSymbolName(null, tmp.getPackagePath(), tmp.getPackageName(), null); + } + + private GoSymbolName(String symbolName) { + this(symbolName, null, null, null); + } + + /** + * Returns the portion the symbol name that is the packagePath (path+packagename), or null + * @return the portion the symbol name that is the packagePath (path+packagename), or null + */ + public String getPackagePath() { + return packagePath; + } + + /** + * Returns portion of the symbol name that is the package name, or null + * @return portion of the symbol name that is the package name, or null + */ + public String getPackageName() { + return packageName; + } + + /** + * Returns portion of the symbol name that is the receiver string (only found when + * the receiver is in the form of "(*typename)"), or null + * @return portion of the symbol name that is the receiver string (only found when + * the receiver is in the form of "(*typename)"), or null + */ + public String getRecieverString() { + return receiverString; + } + + /** + * Returns the full name of the golang symbol + * @return full name of the golang symbol + */ + public String getSymbolName() { + return symbolName; + } + + /** + * Returns the portion of the package path before the package name, eg. "internal/sys" would + * become "internal/". + * + * @return package path, without the trailing package name, or empty string if there is no path + * portion of the string + */ + public String getTruncatedPackagePath() { + return packagePath != null && packageName != null && + packagePath.length() > packageName.length() + ? packagePath.substring(0, packagePath.length() - packageName.length()) + : null; + } + + /** + * Returns a Ghidra {@link Namespace} based on the golang package path. + * + * @param program {@link Program} that will contain the namespace + * @return {@link Namespace} cooresponding to the golang package path, or the program's root + * namespace if no package path information is present + */ + public Namespace getSymbolNamespace(Program program) { + Namespace rootNS = program.getGlobalNamespace(); + if (packagePath != null && !packagePath.isBlank()) { + try { + return program.getSymbolTable() + .getOrCreateNameSpace(rootNS, packagePath, SourceType.IMPORTED); + } + catch (DuplicateNameException | InvalidInputException e) { + // ignore, fall thru + } + } + return rootNS; + } + + /** + * Returns the matching Ghidra function (based on namespace and symbol name). + * + * @param program {@link Program} containing the function + * @return Ghidra {@link Function} + */ + public Function getFunction(Program program) { + Namespace ns = getSymbolNamespace(program); + Symbol sym = SymbolUtilities.getUniqueSymbol(program, getSymbolName(), ns); + Function func = sym instanceof FunctionSymbol ? (Function) sym.getObject() : null; + return func; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java index da7ecca7ec..6e5685203d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java @@ -17,6 +17,7 @@ package ghidra.app.util.bin.format.golang.rtti; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.format.golang.structmapping.*; @@ -57,30 +58,66 @@ public class GoVarlenString implements StructureReader { this.bytes = reader.readNextByteArray(strLen); } + /** + * Returns the string's length + * + * @return string's length + */ public int getStrlen() { return bytes.length; } + /** + * Returns the string length's length (length of the leb128 number) + * + * @return string length's length + */ public int getStrlenLen() { return strlenLen; } + /** + * Returns the raw bytes of the string + * + * @return raw bytes of the string + */ public byte[] getBytes() { return bytes; } + /** + * Returns the string value. + * + * @return string value + */ public String getString() { return new String(bytes, StandardCharsets.UTF_8); } + /** + * Returns the data type that is needed to hold the string length field. + * + * @return data type needed to hold the string length field + */ public DataTypeInstance getStrlenDataType() { return DataTypeInstance.getDataTypeInstance(UnsignedLeb128DataType.dataType, strlenLen, false); } + /** + * Returns the data type that holds the raw string value. + * + * @return data type that holds the raw string value. + */ public DataType getValueDataType() { return new ArrayDataType(CharDataType.dataType, bytes.length, -1, context.getDataTypeMapper().getDTM()); } + @Override + public String toString() { + return String.format("GoVarlenString [context=%s, strlenLen=%s, bytes=%s, getString()=%s]", + context, strlenLen, Arrays.toString(bytes), getString()); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/MethodInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/MethodInfo.java new file mode 100644 index 0000000000..7e0d087106 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/MethodInfo.java @@ -0,0 +1,49 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.util.bin.format.golang.rtti; + +import java.io.IOException; + +import ghidra.program.model.address.Address; +import ghidra.program.model.data.FunctionDefinition; + +/** + * Abstract base for information about type methods and interface methods + */ +public abstract class MethodInfo { + final Address address; + + public MethodInfo(Address address) { + this.address = address; + } + + /** + * Entry point of the method + * + * @return {@link Address} + */ + public Address getAddress() { + return address; + } + + /** + * Function signature of the method. + * + * @return {@link FunctionDefinition} + * @throws IOException if error reading method information + */ + abstract public FunctionDefinition getSignature() throws IOException; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java index bb7dc84b85..d2f1a305fb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java @@ -15,34 +15,51 @@ */ package ghidra.app.util.bin.format.golang.rtti.types; +import java.io.IOException; import java.util.Set; -import java.io.IOException; - import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler; import ghidra.program.model.data.ArrayDataType; import ghidra.program.model.data.DataType; +/** + * {@link GoType} structure that defines an array. + */ @StructureMapping(structureName = "runtime.arraytype") public class GoArrayType extends GoType { @FieldMapping + @MarkupReference("getElement") private long elem; // pointer to element type @FieldMapping + @MarkupReference("getSliceType") private long slice; // pointer to slice type @FieldMapping private long len; public GoArrayType() { + // empty } + /** + * Returns a reference to the {@link GoType} of the elements of this array. + * + * @return reference to the {@link GoType} of the elements of this array + * @throws IOException if error reading data + */ @Markup public GoType getElement() throws IOException { return programContext.getGoType(elem); } + /** + * Returns a reference to the {@link GoType} that defines the slice version of this array. + * @return reference to the {@link GoType} that defines the slice version of this array + * @throws IOException if error reading data + */ @Markup public GoType getSliceType() throws IOException { return programContext.getGoType(slice); @@ -74,4 +91,30 @@ public class GoArrayType extends GoType { return true; } + @Override + public String getStructureNamespace() throws IOException { + String packagePath = getPackagePathString(); + if (packagePath != null && !packagePath.isEmpty()) { + return packagePath; + } + GoType elementType = getElement(); + if (elementType != null) { + return elementType.getStructureNamespace(); + } + return super.getStructureNamespace(); + } + + @Override + protected String getTypeDeclString() throws IOException { + // type CustomArraytype [elementcount]elementType + String selfName = typ.getName(); + String elemName = programContext.getGoTypeName(elem); + String arrayDefStr = "[%d]%s".formatted(len, elemName); + String defStrWithLinks = "[%d]%s".formatted(len, + AddressAnnotatedStringHandler.createAddressAnnotationString(elem, elemName)); + boolean hasName = !arrayDefStr.equals(selfName); + + return "type %s%s".formatted(hasName ? selfName + " " : "", defStrWithLinks); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoBaseType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoBaseType.java index 46d7c4841b..18b3ab4238 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoBaseType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoBaseType.java @@ -15,9 +15,8 @@ */ package ghidra.app.util.bin.format.golang.rtti.types; -import java.util.Set; - import java.io.IOException; +import java.util.Set; import ghidra.app.util.bin.format.golang.rtti.GoName; import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; @@ -29,7 +28,7 @@ import ghidra.app.util.bin.format.golang.structmapping.*; * The in-memory instance will typically be part of a specialized type structure, depending * on the 'kind' of this type. *

    - * Additionally, there will be an GoUncommonType structure immediately after this type, if + * Additionally, there can be an {@link GoUncommonType} structure immediately after this type, if * the uncommon bit is set in tflag. *

    *

    @@ -60,43 +59,85 @@ public class GoBaseType {
     	private int kind;
     
     	@FieldMapping
    -	@MarkupReference("name")
    +	@MarkupReference("getGoName")
     	private long str; // an offset relative to containing moduledata's type base addr
     
     	@FieldMapping
     	@MarkupReference
     	private long ptrToThis;	// an offset relative to containing moduledata's type base addr
     
    +	/**
    +	 * Returns the size of the type being defined by this structure.
    +	 * 
    +	 * @return size of the type being defined
    +	 */
     	public long getSize() {
     		return size;
     	}
     
    +	/**
    +	 * Returns the {@link GoKind} enum assigned to this type definition.
    +	 * 
    +	 * @return {@link GoKind} enum assigned to this type definition
    +	 */
     	public GoKind getKind() {
     		return GoKind.parseByte(kind);
     	}
     
    +	/**
    +	 * Returns the {@link GoTypeFlag}s assigned to this type definition.
    +	 * @return {@link GoTypeFlag}s assigned to this type definition
    +	 */
     	public Set getFlags() {
     		return GoTypeFlag.parseFlags(tflag);
     	}
     
    +	/**
    +	 * Returns the raw flag value.
    +	 * 
    +	 * @return raw flag value
    +	 */
     	public int getTflag() {
     		return tflag;
     	}
     
    +	/**
    +	 * Returns true if this type definition's flags indicate there is a following GoUncommon
    +	 * structure.
    +	 * 
    +	 * @return true if this type definition's flags indicate there is a following GoUncommon struct
    +	 */
     	public boolean hasUncommonType() {
     		return GoTypeFlag.Uncommon.isSet(tflag);
     	}
     
    +	/**
    +	 * Returns the name of this type.
    +	 * 
    +	 * @return name of this type, as a {@link GoName}
    +	 * @throws IOException if error reading data
    +	 */
     	@Markup
    -	public GoName getName() throws IOException {
    +	public GoName getGoName() throws IOException {
     		return programContext.resolveNameOff(context.getStructureStart(), str);
     	}
     
    -	public String getNameString() throws IOException {
    -		String s = getName().getName();
    -		return GoTypeFlag.ExtraStar.isSet(tflag) ? s.substring(1) : s;
    +	/**
    +	 * Returns the name of this type.
    +	 * 
    +	 * @return String name of this type
    +	 */
    +	public String getName() {
    +		String s = programContext.getSafeName(this::getGoName, this, "").getName();
    +		return GoTypeFlag.ExtraStar.isSet(tflag) && s.startsWith("*") ? s.substring(1) : s;
     	}
     
    +	/**
    +	 * Returns a reference to the {@link GoType} that represents a pointer to this type.
    +	 * 
    +	 * @return reference to the {@link GoType} that represents a pointer to this type
    +	 * @throws IOException if error reading
    +	 */
     	@Markup
     	public GoType getPtrToThis() throws IOException {
     		return programContext.resolveTypeOff(context.getStructureStart(), ptrToThis);
    diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoChanType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoChanType.java
    index 9d40e0c01e..f30fb2173a 100644
    --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoChanType.java
    +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoChanType.java
    @@ -15,27 +15,35 @@
      */
     package ghidra.app.util.bin.format.golang.rtti.types;
     
    -import java.util.Set;
    -
     import java.io.IOException;
    +import java.util.Set;
     
     import ghidra.app.util.bin.format.golang.structmapping.*;
     import ghidra.program.model.data.*;
     import ghidra.util.Msg;
     
    +/**
    + * A {@link GoType} structure that defines a go channel
    + */
     @StructureMapping(structureName = "runtime.chantype")
     public class GoChanType extends GoType {
     
     	@FieldMapping
    -	@MarkupReference("element")
    +	@MarkupReference("getElement")
     	private long elem;
     
     	@FieldMapping
     	private long dir;
     
     	public GoChanType() {
    +		// empty
     	}
     
    +	/**
    +	 * Returns a reference to the {@link GoType} that defines the elements this channel uses
    +	 * @return reference to the {@link GoType} that defines the elements this channel uses
    +	 * @throws IOException if error reading type
    +	 */
     	@Markup
     	public GoType getElement() throws IOException {
     		return programContext.getGoType(elem);
    @@ -50,13 +58,14 @@ public class GoChanType extends GoType {
     			return programContext.getDTM().getPointer(null);
     		}
     
    -		DataType chanDT = chanGoType.recoverDataType();
    +		DataType chanDT = programContext.getRecoveredType(chanGoType);
     		Pointer ptrChanDt = programContext.getDTM().getPointer(chanDT);
     		if (typ.getSize() != ptrChanDt.getLength()) {
     			Msg.warn(this, "Size mismatch between chan type and recovered type");
     		}
    -		TypedefDataType typedef = new TypedefDataType(programContext.getRecoveredTypesCp(),
    -			getStructureName(), ptrChanDt, programContext.getDTM());
    +		TypedefDataType typedef =
    +			new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()),
    +				getUniqueTypename(), ptrChanDt, programContext.getDTM());
     		return typedef;
     
     	}
    diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoFuncType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoFuncType.java
    index 639f003ebd..87da2072cf 100644
    --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoFuncType.java
    +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoFuncType.java
    @@ -17,7 +17,6 @@ package ghidra.app.util.bin.format.golang.rtti.types;
     
     import java.io.IOException;
     import java.util.*;
    -import java.util.stream.Collectors;
     
     import ghidra.app.util.bin.format.golang.GoFunctionMultiReturn;
     import ghidra.app.util.bin.format.golang.rtti.GoSlice;
    @@ -25,9 +24,24 @@ import ghidra.app.util.bin.format.golang.structmapping.*;
     import ghidra.program.model.address.Address;
     import ghidra.program.model.data.*;
     
    +/**
    + * A {@link GoType} structure that defines a function type.
    + */
     @StructureMapping(structureName = "runtime.functype")
     public class GoFuncType extends GoType {
     
    +	/**
    +	 * Converts a ptr-to-ptr-to-funcdef to the base funcdef type.
    +	 * 
    +	 * @param dt ghidra {@link DataType}
    +	 * @return {@link FunctionDefinition} that was pointed to by specified data type, or null
    +	 */
    +	public static FunctionDefinition unwrapFunctionDefinitionPtrs(DataType dt) {
    +		return dt != null && dt instanceof Pointer ptrDT &&
    +			ptrDT.getDataType() instanceof Pointer ptrptrDT &&
    +			ptrptrDT.getDataType() instanceof FunctionDefinition funcDef ? funcDef : null;
    +	}
    +
     	@FieldMapping
     	private int inCount; // uint16
     
    @@ -35,30 +49,47 @@ public class GoFuncType extends GoType {
     	private int outCount; // uint16
     
     	public GoFuncType() {
    +		// empty
     	}
     
    +	/**
    +	 * Returns true if this function type is defined to be vararg
    +	 * @return true if this function type is defined to be vararg
    +	 */
     	public boolean isVarArg() {
     		return (outCount & 0x8000) != 0;
     	}
     
    +	/**
    +	 * Returns the number of inbound parameters
    +	 * @return number of inbound parameters
    +	 */
     	public int getInCount() {
     		return inCount;
     	}
     
    +	/**
    +	 * Returns the number of outbound result values
    +	 * @return number of outbound result values
    +	 */
     	public int getOutCount() {
     		return outCount & 0x7fff;
     	}
     
    +	/**
    +	 * Returns the total number of in and out parameters
    +	 * @return total number of in and out parameters
    +	 */
     	public int getParamCount() {
     		return inCount + (outCount & 0x7fff);
     	}
     
    -	public List
    getParamTypeAddrs() throws IOException { + private List
    getParamTypeAddrs() throws IOException { GoSlice slice = getParamListSlice(); long[] typeOffsets = slice.readUIntList(programContext.getPtrSize()); return Arrays.stream(typeOffsets) .mapToObj(programContext::getDataAddress) - .collect(Collectors.toList()); + .toList(); } private GoSlice getParamListSlice() { @@ -66,6 +97,11 @@ public class GoFuncType extends GoType { return new GoSlice(getOffsetEndOfFullType(), count, count, programContext); } + /** + * Returns a list of {@link GoType}s for each parameter + * @return list of {@link GoType}s for each parameter + * @throws IOException if error read type info + */ @Markup public List getParamTypes() throws IOException { return getParamTypeAddrs().stream() @@ -77,18 +113,31 @@ public class GoFuncType extends GoType { return null; } }) - .collect(Collectors.toList()); + .toList(); } @Override public void additionalMarkup(MarkupSession session) throws IOException { GoSlice slice = getParamListSlice(); - slice.markupArray(getStructureLabel() + "_paramlist", GoBaseType.class, true, session); + slice.markupArray(getStructureLabel() + "_paramlist", getStructureNamespace(), + GoBaseType.class, true, session); } - public String getFuncPrototypeString(String funcName) throws IOException { + /** + * Returns a string that describes the function type as a golang-ish function decl. + * + * @param funcName optional name of a function + * @param receiverString optional receiver decl string + * @return golang func decl string + * @throws IOException if error reading parameter type info + */ + public String getFuncPrototypeString(String funcName, String receiverString) + throws IOException { StringBuilder sb = new StringBuilder(); sb.append("func"); + if (receiverString != null && !receiverString.isBlank()) { + sb.append(" (").append(receiverString).append(")"); + } if (funcName != null && !funcName.isBlank()) { sb.append(" ").append(funcName); } @@ -102,7 +151,7 @@ public class GoFuncType extends GoType { if (i != 0) { sb.append(", "); } - sb.append(paramType.getNameString()); + sb.append(paramType.getName()); } sb.append(")"); if (!outParamTypes.isEmpty()) { @@ -112,7 +161,7 @@ public class GoFuncType extends GoType { if (i != 0) { sb.append(", "); } - sb.append(paramType.getNameString()); + sb.append(paramType.getName()); } sb.append(")"); } @@ -121,13 +170,15 @@ public class GoFuncType extends GoType { @Override public DataType recoverDataType() throws IOException { - String name = typ.getNameString(); + String name = getUniqueTypename(); DataTypeManager dtm = programContext.getDTM(); - FunctionDefinitionDataType funcDef = - new FunctionDefinitionDataType(programContext.getRecoveredTypesCp(), name, dtm); + FunctionDefinitionDataType funcDef = new FunctionDefinitionDataType( + programContext.getRecoveredTypesCp(getPackagePathString()), name, dtm); Pointer funcDefPtr = dtm.getPointer(funcDef); - programContext.cacheRecoveredDataType(this, funcDefPtr); + Pointer funcDefPtrPtr = dtm.getPointer(funcDefPtr); + // pre-push empty funcdef type into cache to prevent endless recursive loops + programContext.cacheRecoveredDataType(this, funcDefPtrPtr); List paramTypes = getParamTypes(); List inParamTypes = paramTypes.subList(0, inCount); @@ -136,7 +187,7 @@ public class GoFuncType extends GoType { List params = new ArrayList<>(); for (int i = 0; i < inParamTypes.size(); i++) { GoType paramType = inParamTypes.get(i); - DataType paramDT = paramType.recoverDataType(); + DataType paramDT = programContext.getRecoveredType(paramType); params.add(new ParameterDefinitionImpl(null, paramDT, null)); } DataType returnDT; @@ -144,25 +195,26 @@ public class GoFuncType extends GoType { returnDT = VoidDataType.dataType; } else if (outParamTypes.size() == 1) { - returnDT = outParamTypes.get(0).recoverDataType(); + returnDT = programContext.getRecoveredType(outParamTypes.get(0)); } else { List paramDataTypes = recoverTypes(outParamTypes); GoFunctionMultiReturn multiReturn = new GoFunctionMultiReturn( - programContext.getRecoveredTypesCp(), name, paramDataTypes, dtm, null); + programContext.getRecoveredTypesCp(getPackagePathString()), name, paramDataTypes, + dtm, null); returnDT = multiReturn.getStruct(); } funcDef.setArguments(params.toArray(ParameterDefinition[]::new)); funcDef.setReturnType(returnDT); - return funcDefPtr; + return funcDefPtrPtr; } private List recoverTypes(List types) throws IOException { List result = new ArrayList<>(); for (GoType type : types) { - result.add(type.recoverDataType()); + result.add(programContext.getRecoveredType(type)); } return result; } @@ -181,13 +233,8 @@ public class GoFuncType extends GoType { } @Override - public String toString() { - try { - return getFuncPrototypeString(null); - } - catch (IOException e) { - return super.toString(); - } + protected String getTypeDeclString() throws IOException { + return getFuncPrototypeString(null, null); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoIMethod.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoIMethod.java index b20c57f4ca..8f6b2f1b77 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoIMethod.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoIMethod.java @@ -17,9 +17,10 @@ package ghidra.app.util.bin.format.golang.rtti.types; import java.io.IOException; -import ghidra.app.util.bin.format.golang.rtti.GoName; -import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; +import ghidra.app.util.bin.format.golang.rtti.*; import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.FunctionDefinition; @StructureMapping(structureName = "runtime.imethod") public class GoIMethod implements StructureMarkup { @@ -31,22 +32,22 @@ public class GoIMethod implements StructureMarkup { private StructureContext context; @FieldMapping - @MarkupReference - @EOLComment("nameString") + @MarkupReference("getGoName") + @EOLComment("getName") private long name; @FieldMapping - @MarkupReference("type") + @MarkupReference("getType") private long ityp; @Markup - public GoName getName() throws IOException { + public GoName getGoName() throws IOException { return programContext.resolveNameOff(context.getStructureStart(), name); } - public String getNameString() throws IOException { - GoName n = getName(); - return n != null ? n.getName() : "_blank_"; + public String getName() { + GoName n = programContext.getSafeName(this::getGoName, this, "unnamed_imethod"); + return n.getName(); } @Markup @@ -61,9 +62,38 @@ public class GoIMethod implements StructureMarkup { @Override public String getStructureName() throws IOException { - return getNameString(); + return getName(); } + @Override + public String toString() { + return String.format("GoIMethod [getName()=%s, getStructureContext()=%s]", getName(), + getStructureContext()); + } + + public static class GoIMethodInfo extends MethodInfo { + GoItab itab; + GoIMethod imethod; + + public GoIMethodInfo(GoItab itab, GoIMethod imethod, Address address) { + super(address); + this.itab = itab; + this.imethod = imethod; + } + + public GoItab getItab() { + return itab; + } + + public GoIMethod getImethod() { + return imethod; + } + + @Override + public FunctionDefinition getSignature() throws IOException { + return itab.getSignatureFor(imethod); + } + } } /* struct runtime.imethod // Length: 8 Alignment: 4 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoInterfaceType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoInterfaceType.java index cb6cb05501..26aa5fc2be 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoInterfaceType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoInterfaceType.java @@ -23,41 +23,57 @@ import ghidra.app.util.bin.format.golang.rtti.*; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.program.model.data.DataType; import ghidra.program.model.data.TypedefDataType; +import ghidra.util.exception.CancelledException; +/** + * A {@link GoType} structure that defines a golang interface. + */ @StructureMapping(structureName = "runtime.interfacetype") public class GoInterfaceType extends GoType { @FieldMapping - @MarkupReference("pkgPath") + @MarkupReference("getPkgPath") private long pkgpath; // pointer to name @FieldMapping private GoSlice mhdr; public GoInterfaceType() { + // empty } + /** + * Returns the package path of this type, referenced via the pkgpath field's markup annotation + * + * @return package path {@link GoName}a + * @throws IOException if error reading + */ @Markup public GoName getPkgPath() throws IOException { return programContext.getGoName(pkgpath); } - public String getPkgPathString() throws IOException { - GoName n = getPkgPath(); - return n != null ? n.getName() : ""; - } - + /** + * Returns a slice containing the methods of this interface. + * + * @return slice containing the methods of this interface + */ public GoSlice getMethodsSlice() { return mhdr; } + /** + * Returns the methods defined by this interface + * @return methods defined by this interface + * @throws IOException if error reading data + */ public List getMethods() throws IOException { return mhdr.readList(GoIMethod.class); } @Override - public void additionalMarkup(MarkupSession session) throws IOException { - mhdr.markupArray(null, GoIMethod.class, false, session); + public void additionalMarkup(MarkupSession session) throws IOException, CancelledException { + mhdr.markupArray(null, getStructureNamespace(), GoIMethod.class, false, session); mhdr.markupArrayElements(GoIMethod.class, session); } @@ -65,10 +81,10 @@ public class GoInterfaceType extends GoType { public DataType recoverDataType() throws IOException { DataType dt = programContext.getStructureDataType(GoIface.class); - String name = typ.getNameString(); + String name = getUniqueTypename(); if (!dt.getName().equals(name)) { - dt = new TypedefDataType(programContext.getRecoveredTypesCp(), name, dt, - programContext.getDTM()); + dt = new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()), + name, dt, programContext.getDTM()); } return dt; } @@ -76,14 +92,15 @@ public class GoInterfaceType extends GoType { @Override public String getMethodListString() throws IOException { StringBuilder sb = new StringBuilder(); + String ifaceName = getNameWithPackageString(); for (GoIMethod imethod : getMethods()) { if (!sb.isEmpty()) { sb.append("\n"); } - String methodStr = imethod.getNameString(); + String methodStr = imethod.getName(); GoType type = imethod.getType(); if (type instanceof GoFuncType funcType) { - methodStr = funcType.getFuncPrototypeString(methodStr); + methodStr = funcType.getFuncPrototypeString(methodStr, ifaceName); } else { methodStr = "func %s()".formatted(methodStr); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoKind.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoKind.java index 89ef4a3fb7..4d8f8c3928 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoKind.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoKind.java @@ -15,6 +15,9 @@ */ package ghidra.app.util.bin.format.golang.rtti.types; +/** + * Enum defining the various golang primitive types + */ public enum GoKind { invalid, Bool, // 1 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMapType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMapType.java index 47705393dc..290f21b257 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMapType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMapType.java @@ -20,6 +20,7 @@ import java.util.Set; import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler; import ghidra.program.model.data.*; import ghidra.util.Msg; @@ -33,12 +34,15 @@ import ghidra.util.Msg; public class GoMapType extends GoType { @FieldMapping + @MarkupReference("getKey") private long key; // ptr to type @FieldMapping + @MarkupReference("getElement") private long elem; // ptr to type @FieldMapping + @MarkupReference("getBucket") private long bucket; // ptr to type @FieldMapping @@ -60,16 +64,34 @@ public class GoMapType extends GoType { // empty } + /** + * Returns the GoType that defines the map's key, referenced by the key field's markup annotation + * + * @return GoType that defines the map's key + * @throws IOException if error reading data + */ @Markup public GoType getKey() throws IOException { return programContext.getGoType(key); } + /** + * Returns the GoType that defines the map's element, referenced by the element field's markup annotation + * + * @return GoType that defines the map's element + * @throws IOException if error reading data + */ @Markup public GoType getElement() throws IOException { return programContext.getGoType(elem); } + /** + * Returns the GoType that defines the map's bucket, referenced by the bucket field's markup annotation + * + * @return GoType that defines the map's bucket + * @throws IOException if error reading data + */ @Markup public GoType getBucket() throws IOException { return programContext.getGoType(bucket); @@ -83,13 +105,14 @@ public class GoMapType extends GoType { // a void* return programContext.getDTM().getPointer(null); } - DataType mapDT = mapGoType.recoverDataType(); + DataType mapDT = programContext.getRecoveredType(mapGoType); Pointer ptrMapDt = programContext.getDTM().getPointer(mapDT); if (typ.getSize() != ptrMapDt.getLength()) { Msg.warn(this, "Size mismatch between map type and recovered type"); } - TypedefDataType typedef = new TypedefDataType(programContext.getRecoveredTypesCp(), - getStructureName(), ptrMapDt, programContext.getDTM()); + TypedefDataType typedef = + new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()), + getUniqueTypename(), ptrMapDt, programContext.getDTM()); return typedef; } @@ -112,4 +135,20 @@ public class GoMapType extends GoType { } return true; } + + @Override + protected String getTypeDeclString() throws IOException { + // type CustomMaptype map[keykey]valuetype + String selfName = typ.getName(); + String keyName = programContext.getGoTypeName(key); + String elemName = programContext.getGoTypeName(elem); + String defStr = "map[%s]%s".formatted(keyName, elemName); + String defStrWithLinks = "map[%s]%s".formatted( + AddressAnnotatedStringHandler.createAddressAnnotationString(key, keyName), + AddressAnnotatedStringHandler.createAddressAnnotationString(elem, elemName)); + boolean hasName = !defStr.equals(selfName); + + return "type %s%s".formatted(hasName ? selfName + " " : "", defStrWithLinks); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMethod.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMethod.java index dd247671c1..b67e235e48 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMethod.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMethod.java @@ -16,12 +16,18 @@ package ghidra.app.util.bin.format.golang.rtti.types; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; -import ghidra.app.util.bin.format.golang.rtti.GoName; -import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; +import ghidra.app.util.bin.format.golang.rtti.*; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.program.model.address.Address; +import ghidra.program.model.data.FunctionDefinition; +import ghidra.util.NumericUtilities; +/** + * Structure that defines a method for a GoType, found in the type's {@link GoUncommonType} struct. + */ @StructureMapping(structureName = "runtime.method") public class GoMethod implements StructureMarkup { @ContextField @@ -31,32 +37,58 @@ public class GoMethod implements StructureMarkup { private StructureContext context; @FieldMapping - @MarkupReference - @EOLComment("nameString") + @MarkupReference("getGoName") + @EOLComment("getName") private long name; // nameOff @FieldMapping - @MarkupReference("type") - private long mtyp; // typeOff + @MarkupReference("getType") + private long mtyp; // typeOff - function definition @FieldMapping @MarkupReference - private long ifn; // textOff + private long ifn; // textOff, address of version of method called via the interface @FieldMapping @MarkupReference - private long tfn; // textOff + private long tfn; // textOff, address of version of method called normally + /** + * Returns the name of this method. + * + * @return name of this method as a raw GoName value + * @throws IOException if error reading + */ @Markup - public GoName getName() throws IOException { + public GoName getGoName() throws IOException { return programContext.resolveNameOff(context.getStructureStart(), name); } - public String getNameString() throws IOException { - GoName n = getName(); - return n != null ? n.getName() : "_blank_"; + /** + * Returns the name of this method. + * + * @return name of this method + */ + public String getName() { + GoName n = programContext.getSafeName(this::getGoName, this, "unnamed_method"); + return n.getName(); } + /** + * Returns true if the funcdef is missing for this method. + * + * @return true if the funcdef is missing for this method + */ + public boolean isSignatureMissing() { + return mtyp == 0 || mtyp == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG || mtyp == -1; + } + + /** + * Return the {@link GoType} that defines the funcdef / func signature. + * + * @return {@link GoType} that defines the funcdef / func signature + * @throws IOException if error reading data + */ @Markup public GoType getType() throws IOException { return programContext.resolveTypeOff(context.getStructureStart(), mtyp); @@ -68,16 +100,90 @@ public class GoMethod implements StructureMarkup { } @Override - public String getStructureName() throws IOException { - return getNameString(); + public String getStructureName() { + return getName(); } + /** + * Returns the address of the version of the function that is called via the interface. + * + * @return address of the version of the function that is called via the interface + */ public Address getIfn() { return programContext.resolveTextOff(context.getStructureStart(), ifn); } + /** + * Returns the address of the version of the function that is called normally. + * + * @return address of the version of the function that is called normally + */ public Address getTfn() { return programContext.resolveTextOff(context.getStructureStart(), tfn); } + /** + * Returns a list of {@link GoMethodInfo}s containing the ifn and tfn values (if present). + * + * @param containingType {@link GoType} that contains this method + * @return list of {@link GoMethodInfo} instances representing the ifn and tfn values if present + */ + public List getMethodInfos(GoType containingType) { + List results = new ArrayList<>(2); + Address addr = getTfn(); + if (addr != null) { + results.add(new GoMethodInfo(containingType, this, addr)); + } + addr = getIfn(); + if (addr != null) { + results.add(new GoMethodInfo(containingType, this, addr)); + } + return results; + } + + @Override + public String toString() { + return String.format( + "GoMethod [context=%s, getName()=%s, getIfn()=%s, getTfn()=%s]", context, getName(), + getIfn(), getTfn()); + } + + //---------------------------------------------------------------------------------------- + + public class GoMethodInfo extends MethodInfo { + GoType type; + GoMethod method; + + public GoMethodInfo(GoType type, GoMethod method, Address address) { + super(address); + this.type = type; + this.method = method; + } + + public GoType getType() { + return type; + } + + public GoMethod getMethod() { + return method; + } + + public boolean isIfn(Address funcAddr) { + return funcAddr.equals(method.getIfn()); + } + + public boolean isTfn(Address funcAddr) { + return funcAddr.equals(method.getTfn()); + } + + @Override + public FunctionDefinition getSignature() throws IOException { + return type.getMethodSignature(method, false); + } + + public FunctionDefinition getPartialSignature() throws IOException { + return type.getMethodSignature(method, true); + } + + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPlainType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPlainType.java index 36ce223a2e..2b1de93894 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPlainType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPlainType.java @@ -29,6 +29,8 @@ import ghidra.util.Msg; *

    * To coerce java inheritance and structmapping features to match the layout of go rtti type structs, * this class is constructed strangely. + *

    + * {@link GoType} structure that defines a built-in primitive type. */ @StructureMapping(structureName = "runtime._type") public class GoPlainType extends GoType implements StructureReader { @@ -65,9 +67,10 @@ public class GoPlainType extends GoType implements StructureReader { dt = super.recoverDataType(); } - String name = typ.getNameString(); + String name = getUniqueTypename(); if (!dt.getName().equalsIgnoreCase(name)) { - dt = new TypedefDataType(programContext.getRecoveredTypesCp(), name, dt, dtm); + dt = new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()), + name, dt, dtm); } if (dt.getLength() != typ.getSize()) { Msg.warn(this, @@ -82,4 +85,5 @@ public class GoPlainType extends GoType implements StructureReader { return super.discoverGoTypes(discoveredTypes); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPointerType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPointerType.java index b832c3eb7a..d6aaa1daf8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPointerType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPointerType.java @@ -15,23 +15,33 @@ */ package ghidra.app.util.bin.format.golang.rtti.types; +import java.io.IOException; import java.util.Set; -import java.io.IOException; - import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler; import ghidra.program.model.data.DataType; import ghidra.program.model.data.PointerDataType; +/** + * {@link GoType} structure that defines a pointer. + */ @StructureMapping(structureName = "runtime.ptrtype") public class GoPointerType extends GoType { @FieldMapping - @MarkupReference("element") + @MarkupReference("getElement") private long elem; public GoPointerType() { + // empty } + /** + * Returns a reference to the element's type. + * + * @return reference to the element's type + * @throws IOException if error reading data + */ @Markup public GoType getElement() throws IOException { return programContext.getGoType(elem); @@ -58,4 +68,25 @@ public class GoPointerType extends GoType { } return true; } + + @Override + public String getStructureNamespace() throws IOException { + GoType elementType = getElement(); + return elementType != null + ? elementType.getStructureNamespace() + : super.getStructureNamespace(); + } + + @Override + protected String getTypeDeclString() throws IOException { + // type PointerTypeName *elementType + String selfName = getName(); + String elemName = programContext.getGoTypeName(elem); + String defStr = "*" + elemName; + String defStrWithLinks = + "*" + AddressAnnotatedStringHandler.createAddressAnnotationString(elem, elemName); + boolean hasName = !defStr.equals(selfName); + return "type %s%s".formatted(hasName ? selfName + " " : "", defStrWithLinks); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoSliceType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoSliceType.java index c3373033e1..e97b1dd8aa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoSliceType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoSliceType.java @@ -20,6 +20,7 @@ import java.util.Set; import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler; import ghidra.program.model.data.*; /** @@ -32,12 +33,18 @@ import ghidra.program.model.data.*; public class GoSliceType extends GoType { @FieldMapping - @MarkupReference("element") + @MarkupReference("getElement") private long elem; public GoSliceType() { + // empty } + /** + * Returns a reference to the element's type. + * @return reference to the element's type + * @throws IOException if error reading data + */ @Markup public GoType getElement() throws IOException { return programContext.getGoType(elem); @@ -45,13 +52,14 @@ public class GoSliceType extends GoType { @Override public DataType recoverDataType() throws IOException { - StructureDataType sliceDT = new StructureDataType(programContext.getRecoveredTypesCp(), - typ.getNameString(), 0, programContext.getDTM()); + StructureDataType sliceDT = + new StructureDataType(programContext.getRecoveredTypesCp(getPackagePathString()), + getUniqueTypename(), 0, programContext.getDTM()); programContext.cacheRecoveredDataType(this, sliceDT); // fixup the generic void* field with the specific element* type GoType elementType = getElement(); - DataType elementDT = elementType.recoverDataType(); + DataType elementDT = programContext.getRecoveredType(elementType); Pointer elementPtrDT = programContext.getDTM().getPointer(elementDT); Structure genericSliceDT = programContext.getGenericSliceDT(); @@ -77,4 +85,29 @@ public class GoSliceType extends GoType { return true; } + @Override + public String getStructureNamespace() throws IOException { + String packagePath = getPackagePathString(); + if (packagePath != null) { + return packagePath; + } + GoType elementType = getElement(); + return elementType != null + ? elementType.getStructureNamespace() + : super.getStructureNamespace(); + } + + @Override + protected String getTypeDeclString() throws IOException { + // type CustomSliceType []elementType + String selfName = typ.getName(); + String elemName = programContext.getGoTypeName(elem); + String defStr = "[]%s".formatted(elemName); + String defStrWithLinks = "[]%s".formatted( + AddressAnnotatedStringHandler.createAddressAnnotationString(elem, elemName)); + boolean hasName = !defStr.equals(selfName); + + return "type %s%s".formatted(hasName ? selfName + " " : "", defStrWithLinks); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java index 7abd1c0198..843a117cca 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java @@ -21,6 +21,9 @@ import ghidra.app.util.bin.format.golang.rtti.GoName; import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; import ghidra.app.util.bin.format.golang.structmapping.*; +/** + * Structure used to define a field in a {@link GoStructType struct type}. + */ @StructureMapping(structureName = "runtime.structfield") public class GoStructField { @@ -31,12 +34,12 @@ public class GoStructField { private StructureContext context; @FieldMapping - @MarkupReference - @EOLComment("nameString") + @MarkupReference("getGoName") + @EOLComment("getName") private long name; // direct ptr to GoName @FieldMapping - @MarkupReference("type") + @MarkupReference("getType") private long typ; // direct ptr to GoType @FieldMapping(optional = true) //<=1.18 @@ -45,23 +48,44 @@ public class GoStructField { @FieldMapping(optional = true) //>=1.19 private long offset; + /** + * Returns the name of this field. + * + * @return name of this field as it's raw GoName value + * @throws IOException if error reading + */ @Markup - public GoName getName() throws IOException { + public GoName getGoName() throws IOException { return name != 0 ? context.getDataTypeMapper().readStructure(GoName.class, name) : null; } + /** + * Returns the type of this field. + * + * @return type of this field + * @throws IOException if error reading + */ @Markup public GoType getType() throws IOException { return programContext.getGoType(typ); } + /** + * Setter called by offsetAnon field's serialization, referred by fieldmapping annotation. + * + * @param offsetAnon value + */ public void setOffsetAnon(long offsetAnon) { this.offsetAnon = offsetAnon; this.offset = offsetAnon >> 1; } + /** + * Returns the offset of this field. + * @return offset of this field + */ public long getOffset() { return offset; } @@ -70,9 +94,13 @@ public class GoStructField { // return (offsetAnon & 0x1) != 0; // } - public String getNameString() throws IOException { - GoName n = getName(); - return n != null ? n.getName() : null; + /** + * Returns the name of this field. + * + * @return name of this field + */ + public String getName() { + return programContext.getSafeName(this::getGoName, this, null).getName(); } } /* diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructType.java index f31ec7f527..4a4cab90a3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructType.java @@ -24,6 +24,7 @@ import ghidra.app.util.bin.format.golang.rtti.GoSlice; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.program.model.data.*; import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; /** * Golang type information about a specific structure type. @@ -42,16 +43,45 @@ public class GoStructType extends GoType { // empty } + /** + * Returns the package path of this structure type. + * + * @return package path of this structure type + * @throws IOException if error reading + */ @Markup public GoName getPkgPath() throws IOException { return programContext.getGoName(pkgPath); } - public String getPkgPathString() throws IOException { - GoName n = getPkgPath(); - return n != null ? n.getName() : ""; + /** + * Returns the package path of this structure type + * + * @return package path of this structure type, as a string + */ + @Override + public String getPackagePathString() { + String s = super.getPackagePathString(); // from uncommontype + if (s == null || s.isEmpty()) { + try { + GoName structPP = getPkgPath(); + if (structPP != null) { + s = structPP.getName(); + } + } + catch (IOException e) { + // fall thru, return existing s + } + } + return s; } + /** + * Returns the fields defined by this struct type. + * + * @return list of fields defined by this struct type + * @throws IOException if error reading + */ public List getFields() throws IOException { return fields.readList(GoStructField.class); } @@ -62,33 +92,30 @@ public class GoStructType extends GoType { } @Override - public void additionalMarkup(MarkupSession session) throws IOException { + public void additionalMarkup(MarkupSession session) throws IOException, CancelledException { super.additionalMarkup(session); - fields.markupArray(getStructureLabel() + "_fields", GoStructField.class, false, session); + fields.markupArray(getStructureLabel() + "_fields", getStructureNamespace(), + GoStructField.class, false, session); fields.markupArrayElements(GoStructField.class, session); } @Override public String getTypeDeclString() throws IOException { - String methodListStr = getMethodListString(); - if (methodListStr == null || methodListStr.isEmpty()) { - methodListStr = "// No methods"; - } - else { - methodListStr = "// Methods\n" + methodListStr; + + String pps = getPackagePathString(); + if (pps == null || pps.isEmpty()) { + pps = ""; } return """ // size: %d + // package: %s type %s struct { - %s - %s - } - """.formatted( + %s}""".formatted( typ.getSize(), - typ.getNameString(), - getFieldListString().indent(2), - methodListStr.indent(2)); + pps, + typ.getName(), + getFieldListString().indent(2)); } private String getFieldListString() throws IOException { @@ -99,16 +126,19 @@ public class GoStructType extends GoType { } long offset = field.getOffset(); long fieldSize = field.getType().getBaseType().getSize(); - sb.append("%s %s // %d..%d".formatted(field.getNameString(), - field.getType().getNameString(), offset, offset + fieldSize)); + sb.append("%s %s // %d..%d".formatted(field.getName(), + field.getType().getName(), offset, offset + fieldSize)); } return sb.toString(); } @Override public DataType recoverDataType() throws IOException { - StructureDataType struct = new StructureDataType(programContext.getRecoveredTypesCp(), - typ.getNameString(), (int) typ.getSize(), programContext.getDTM()); + StructureDataType struct = + new StructureDataType(programContext.getRecoveredTypesCp(getPackagePathString()), + getUniqueTypename(), (int) typ.getSize(), programContext.getDTM()); + + // pre-push an empty struct into the cache to prevent endless recursive loops programContext.cacheRecoveredDataType(this, struct); List skippedFields = new ArrayList<>(); @@ -131,7 +161,7 @@ public class GoStructType extends GoType { try { DataType fieldDT = programContext.getRecoveredType(fieldType); struct.replaceAtOffset((int) field.getOffset(), fieldDT, (int) fieldSize, - field.getNameString(), null); + field.getName(), null); } catch (IllegalArgumentException e) { Msg.warn(this, @@ -145,8 +175,8 @@ public class GoStructType extends GoType { if (dtc != null) { String comment = dtc.getComment(); comment = comment == null ? "" : (comment + "\n"); - comment += "Omitted zero-len field: %s=%s".formatted(skippedField.getNameString(), - skippedFieldType.getNameString()); + comment += "Omitted zero-len field: %s=%s".formatted(skippedField.getName(), + skippedFieldType.getName()); dtc.setComment(comment); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoType.java index c359a21cc4..ea61eff210 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoType.java @@ -16,19 +16,22 @@ package ghidra.app.util.bin.format.golang.rtti.types; import java.io.IOException; -import java.util.Map; -import java.util.Set; +import java.util.*; -import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; -import ghidra.app.util.bin.format.golang.rtti.GoSlice; +import ghidra.app.util.bin.format.golang.rtti.*; +import ghidra.app.util.bin.format.golang.rtti.types.GoMethod.GoMethodInfo; import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler; +import ghidra.program.model.address.Address; import ghidra.program.model.data.*; +import ghidra.util.exception.CancelledException; /** * Common abstract base class for GoType classes */ @PlateComment() public abstract class GoType implements StructureMarkup { + //@formatter:off private static final Map> specializedTypeClasses = Map.ofEntries( Map.entry(GoKind.Struct, GoStructType.class), @@ -39,6 +42,7 @@ public abstract class GoType implements StructureMarkup { Map.entry(GoKind.Chan, GoChanType.class), Map.entry(GoKind.Map, GoMapType.class), Map.entry(GoKind.Interface, GoInterfaceType.class)); + //@formatter:on /** * Returns the specific GoType derived class that will handle the go type located at the @@ -70,26 +74,59 @@ public abstract class GoType implements StructureMarkup { @FieldOutput protected GoBaseType typ; + protected GoUncommonType uncommonType; + protected GoBaseType getBaseType() { return typ; } - public String getNameString() throws IOException { - return typ.getNameString(); + /** + * Returns the starting offset of this type, used as an identifier. + * + * @return starting offset of this type + */ + public long getTypeOffset() { + return context.getStructureStart(); + } + + /** + * Returns the name of this type. + * + * @return name of this type + */ + public String getName() { + return typ.getName(); + } + + public String getNameWithPackageString() { + GoSymbolName parsedPackagePath = GoSymbolName.fromPackagePath(getPackagePathString()); + String tpp = Objects.requireNonNullElse(parsedPackagePath.getTruncatedPackagePath(), ""); + return tpp + getName(); + } + + /** + * Returns the package path of this type. + * + * @return package path of this type + */ + public String getPackagePathString() { + try { + return typ.hasUncommonType() ? getUncommonType().getPackagePathString() : ""; + } + catch (IOException e) { + return ""; + } } public String getDebugId() { - return "%s@%s".formatted( - context.getMappingInfo().getDescription(), + return "%s@%s".formatted(context.getMappingInfo().getDescription(), context.getStructureAddress()); } protected long getOffsetEndOfFullType() { - return context.getStructureEnd() + - (typ.hasUncommonType() - ? programContext.getStructureMappingInfo(GoUncommonType.class) - .getStructureLength() - : 0); + return context.getStructureEnd() + (typ.hasUncommonType() + ? programContext.getStructureMappingInfo(GoUncommonType.class).getStructureLength() + : 0); } /** @@ -107,9 +144,30 @@ public abstract class GoType implements StructureMarkup { @Markup public GoUncommonType getUncommonType() throws IOException { - return typ.hasUncommonType() - ? programContext.readStructure(GoUncommonType.class, context.getStructureEnd()) - : null; + if (uncommonType == null && typ.hasUncommonType()) { + uncommonType = + programContext.readStructure(GoUncommonType.class, context.getStructureEnd()); + } + return uncommonType; + } + + /** + * Returns a list of all methods defined on this type. Methods that specify both a + * "tfn" address as well as a "ifn" address will be represented twice. + * + * @return list of MethodInfo's + * @throws IOException if error reading + */ + public List getMethodInfoList() throws IOException { + List results = new ArrayList<>(); + GoUncommonType ut = getUncommonType(); + List methods; + if (ut != null && (methods = ut.getMethods()) != null) { + for (GoMethod method : methods) { + results.addAll(method.getMethodInfos(this)); + } + } + return results; } @Override @@ -119,64 +177,153 @@ public abstract class GoType implements StructureMarkup { @Override public String getStructureName() throws IOException { - return typ.getNameString(); + return getNameWithPackageString(); } @Override - public void additionalMarkup(MarkupSession session) throws IOException { - GoUncommonType uncommonType = getUncommonType(); - if (uncommonType != null) { - GoSlice slice = uncommonType.getMethodsSlice(); - slice.markupArray(getStructureName() + "_methods", GoMethod.class, false, session); + public String getStructureNamespace() throws IOException { + return getPackagePathString(); + } + + @Override + public void additionalMarkup(MarkupSession session) throws IOException, CancelledException { + GoUncommonType ut = getUncommonType(); + if (ut != null) { + GoSlice slice = ut.getMethodsSlice(); + slice.markupArray(getStructureName() + "_methods", getStructureNamespace(), + GoMethod.class, false, session); slice.markupArrayElements(GoMethod.class, session); - session.labelStructure(uncommonType, typ.getNameString() + "_" + - programContext.getStructureDataTypeName(GoUncommonType.class)); + session.labelStructure(ut, + typ.getName() + "_" + + programContext.getStructureDataTypeName(GoUncommonType.class), + getStructureNamespace()); } } - public String getMethodListString() throws IOException { - GoUncommonType uncommonType = getUncommonType(); - if (uncommonType == null || uncommonType.mcount == 0) { - return ""; - } + protected String getImplementsInterfaceString() { StringBuilder sb = new StringBuilder(); - for (GoMethod method : uncommonType.getMethods()) { + for (GoItab goItab : programContext.getInterfacesImplementedByType(this)) { if (!sb.isEmpty()) { sb.append("\n"); } - String methodStr = method.getNameString(); - GoType type = method.getType(); - if (type instanceof GoFuncType funcType) { - methodStr = funcType.getFuncPrototypeString(methodStr); + try { + sb.append(AddressAnnotatedStringHandler.createAddressAnnotationString( + goItab.getInterfaceType().getStructureContext().getStructureAddress(), + goItab.getInterfaceType().getNameWithPackageString())); + sb.append(" "); + sb.append(AddressAnnotatedStringHandler.createAddressAnnotationString( + goItab.getStructureContext().getStructureAddress(), "[itab]")); } - else { - methodStr = "func %s()".formatted(methodStr); + catch (IOException e) { + sb.append("unknown_interface"); } - sb.append(methodStr); } return sb.toString(); } - protected String getTypeDeclString() throws IOException { - String s = "type " + typ.getNameString() + " " + typ.getKind(); - String methodListString = getMethodListString(); - if (!methodListString.isEmpty()) { - s += "\n// Methods\n" + methodListString; + protected String getMethodListString() throws IOException { + GoUncommonType ut = getUncommonType(); + if (ut == null || uncommonType.mcount == 0) { + return ""; } - return s; + String typeName = getName(); + StringBuilder sb = new StringBuilder(); + for (GoMethod method : ut.getMethods()) { + GoType ptrType = typ.getPtrToThis(); + String tfnStr = makeMethodStr(method.getType(), method.getName(), typeName); + String ifnStr = ptrType != null + ? makeMethodStr(method.getType(), method.getName(), ptrType.getName()) + : null; + Address tfnAddr = method.getTfn(); + if (tfnAddr != null) { + sb.append(!sb.isEmpty() ? "\n" : "") + .append(AddressAnnotatedStringHandler.createAddressAnnotationString(tfnAddr, + tfnStr)); + } + Address ifnAddr = method.getIfn(); + if (ifnAddr != null && ifnStr != null) { + sb.append(!sb.isEmpty() ? "\n" : "") + .append(AddressAnnotatedStringHandler.createAddressAnnotationString(ifnAddr, + ifnStr)); + } + if (tfnAddr == null && ifnAddr == null) { + String methodStr = makeMethodStr(method.getType(), method.getName(), typeName); + sb.append(!sb.isEmpty() ? "\n" : "").append(methodStr); + } + } + return sb.toString(); + } + + private String makeMethodStr(GoType methodFuncType, String methodName, + String containingTypeName) throws IOException { + return methodFuncType instanceof GoFuncType funcdefType + ? funcdefType.getFuncPrototypeString(methodName, containingTypeName) + : "func (%s) %s(???)".formatted(containingTypeName, methodName); + } + + /** + * Return a funcdef signature for a method that is attached to this type. + * + * @param method {@link GoMethod} + * @param allowPartial boolean flag, if true, allow returning a partially defined signature + * when the method's funcdef type is not specified + * @return {@link FunctionDefinition} (that contains a receiver parameter), or null if + * the method's funcdef type was not specified and allowPartial was not true + * @throws IOException if error reading type info + */ + public FunctionDefinition getMethodSignature(GoMethod method, boolean allowPartial) + throws IOException { + return programContext.getSpecializedMethodSignature(method.getName(), + method.getType(), programContext.getRecoveredType(this), allowPartial); + } + + /** + * Returns a descriptive string that defines the declaration of this type. + *

    + * This method should be overloaded by more specific types. + * + * @return descriptive string + * @throws IOException if error reading data + */ + protected String getTypeDeclString() throws IOException { + return "type " + typ.getName() + " " + typ.getKind(); } @Override public String toString() { try { - return getTypeDeclString(); + String s = getTypeDeclString(); + + String methodListString = getMethodListString(); + if (!methodListString.isEmpty()) { + s += "\n\n// Methods\n" + methodListString; + } + + String interfaceString = getImplementsInterfaceString(); + if (!interfaceString.isEmpty()) { + s += "\n\n// Interfaces implemented\n" + interfaceString; + } + + return s; } catch (IOException e) { return super.toString(); } } + /** + * Returns the name of this type, after being uniqified against all other types defined in the + * program. + *

    + * See {@link GoRttiMapper#getUniqueGoTypename(GoType)}. + * + * @return name of this type + */ + public String getUniqueTypename() { + return programContext.getUniqueGoTypename(this); + } + /** * Converts a golang RTTI type structure into a Ghidra data type. * @@ -185,17 +332,29 @@ public abstract class GoType implements StructureMarkup { */ public DataType recoverDataType() throws IOException { DataType dt = Undefined.getUndefinedDataType((int) typ.getSize()); - return new TypedefDataType(programContext.getRecoveredTypesCp(), typ.getNameString(), dt, - programContext.getDTM()); + return new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()), + getUniqueTypename(), dt, programContext.getDTM()); } + /** + * Iterates this type, and any types this type refers to, while registering the types with + * the {@link GoRttiMapper} context. + *

    + * This method should be overloaded by derived type classes to add any additional types + * referenced by the derived type. + * + * @param discoveredTypes set of already iterated types + * @return boolean boolean flag, if false the type has already been discovered, if true + * the type was encountered for the first time + * @throws IOException if error reading type info + */ public boolean discoverGoTypes(Set discoveredTypes) throws IOException { if (!discoveredTypes.add(context.getStructureStart())) { return false; } - GoUncommonType uncommonType = getUncommonType(); - if (uncommonType != null) { - for (GoMethod method : uncommonType.getMethods()) { + GoUncommonType ut = getUncommonType(); + if (ut != null) { + for (GoMethod method : ut.getMethods()) { GoType methodType = method.getType(); if (methodType != null) { methodType.discoverGoTypes(discoveredTypes); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeFlag.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeFlag.java index c29d0f244b..beb0677177 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeFlag.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeFlag.java @@ -18,6 +18,9 @@ package ghidra.app.util.bin.format.golang.rtti.types; import java.util.EnumSet; import java.util.Set; +/** + * Enum defining the various bitflags held in a GoType's tflag + */ public enum GoTypeFlag { Uncommon(1 << 0), // 1 ExtraStar(1 << 1), // 2 @@ -26,7 +29,7 @@ public enum GoTypeFlag { private final int value; - private GoTypeFlag(int i) { + GoTypeFlag(int i) { this.value = i; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoUncommonType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoUncommonType.java index 4e514466dc..35eef0ed35 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoUncommonType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoUncommonType.java @@ -22,6 +22,10 @@ import ghidra.app.util.bin.format.golang.rtti.*; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.util.Msg; +/** + * Structure found immediately after a {@link GoType} structure, if it has the uncommon flag + * set. + */ @StructureMapping(structureName = "runtime.uncommontype") public class GoUncommonType { @@ -32,8 +36,8 @@ public class GoUncommonType { private StructureContext context; @FieldMapping(fieldName = "pkgpath") - @MarkupReference("pkgPath") - @EOLComment("packagePathString") + @MarkupReference("getPkgPath") + @EOLComment("getPackagePathString") long pkgpath_nameOff; @FieldMapping @@ -45,20 +49,42 @@ public class GoUncommonType { @FieldMapping long moff; + /** + * Returns the package path of the type. + * + * @return package path of the type + * @throws IOException if error reading data + */ @Markup public GoName getPkgPath() throws IOException { return programContext.resolveNameOff(context.getStructureStart(), pkgpath_nameOff); } + /** + * Returns the package path of the type. + * @return package path of the type, as a string + * @throws IOException if error reading data + */ public String getPackagePathString() throws IOException { GoName pkgPath = getPkgPath(); - return pkgPath != null ? pkgPath.getName() : null; + return pkgPath != null ? pkgPath.getName() : ""; } + /** + * Returns a slice containing the methods defined by the type. + * + * @return slice containing the methods defined by the type + */ public GoSlice getMethodsSlice() { return new GoSlice(context.getFieldLocation(moff), mcount, mcount, programContext); } + /** + * Returns a list of the methods defined by the type. + * + * @return list of the methods defined by the type + * @throws IOException if error reading data + */ public List getMethods() throws IOException { GoSlice slice = getMethodsSlice(); if (!slice.isValid( diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapper.java index 95b5059662..8291943656 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapper.java @@ -88,9 +88,8 @@ public class DataTypeMapper implements AutoCloseable { protected DataTypeMapper(Program program, ResourceFile archiveGDT) throws IOException { this.program = program; this.programDTM = program.getDataTypeManager(); - this.archiveDTM = archiveGDT != null - ? FileDataTypeManager.openFileArchive(archiveGDT, false) - : null; + this.archiveDTM = + archiveGDT != null ? FileDataTypeManager.openFileArchive(archiveGDT, false) : null; } @Override @@ -180,8 +179,7 @@ public class DataTypeMapper implements AutoCloseable { structName = ""; } throw new IOException( - "Missing struct definition %s - %s".formatted(clazz.getSimpleName(), - structName)); + "Missing struct definition %s - %s".formatted(clazz.getSimpleName(), structName)); } StructureMappingInfo structMappingInfo = StructureMappingInfo.fromClass(clazz, structDT); @@ -320,9 +318,8 @@ public class DataTypeMapper implements AutoCloseable { * a structure mapped object */ public StructureContext getStructureContextOfInstance(T structureInstance) { - StructureMappingInfo smi = structureInstance != null - ? getStructureMappingInfo(structureInstance) - : null; + StructureMappingInfo smi = + structureInstance != null ? getStructureMappingInfo(structureInstance) : null; return smi != null ? smi.recoverStructureContext(structureInstance) : null; } @@ -336,14 +333,30 @@ public class DataTypeMapper implements AutoCloseable { * @return {@link Address} of the object, or null if not found or not a supported object */ public Address getAddressOfStructure(T structureInstance) { - StructureMappingInfo smi = structureInstance != null - ? getStructureMappingInfo(structureInstance) - : null; - StructureContext structureContext = smi != null - ? smi.recoverStructureContext(structureInstance) - : null; + StructureMappingInfo smi = + structureInstance != null ? getStructureMappingInfo(structureInstance) : null; + StructureContext structureContext = + smi != null ? smi.recoverStructureContext(structureInstance) : null; + return structureContext != null ? structureContext.getStructureAddress() : null; + } + + /** + * Returns the address of the last byte of a structure. + * + * @param type of object + * @param structureInstance instance of an object that represents something in the program's + * memory + * @return {@link Address} of the last byte of the object, or null if not found + * or not a supported object + */ + public Address getMaxAddressOfStructure(T structureInstance) { + StructureMappingInfo smi = + structureInstance != null ? getStructureMappingInfo(structureInstance) : null; + StructureContext structureContext = + smi != null ? smi.recoverStructureContext(structureInstance) : null; return structureContext != null ? structureContext.getStructureAddress() + .add(structureContext.getStructureLength() - 1) : null; } @@ -359,7 +372,26 @@ public class DataTypeMapper implements AutoCloseable { */ public T readStructure(Class structureClass, BinaryReader structReader) throws IOException { - StructureContext structureContext = createStructureContext(structureClass, structReader); + return readStructure(structureClass, null, structReader); + } + + /** + * Reads a structure mapped object from the current position of the specified BinaryReader. + * + * @param type of object + * @param structureClass structure mapped object class + * @param containingFieldDataType optional, data type of the structure field that contained the + * object instance that is being read (may be different than the data type that was specified in + * the matching {@link StructureMappingInfo}) + * @param structReader {@link BinaryReader} positioned at the start of an object + * @return new object instance of type T + * @throws IOException if error reading + * @throws IllegalArgumentException if specified structureClass is not valid + */ + public T readStructure(Class structureClass, DataType containingFieldDataType, + BinaryReader structReader) throws IOException { + StructureContext structureContext = + createStructureContext(structureClass, containingFieldDataType, structReader); T result = structureContext.readNewInstance(); return result; @@ -454,13 +486,29 @@ public class DataTypeMapper implements AutoCloseable { } private StructureContext createStructureContext(Class structureClass, - BinaryReader reader) throws IllegalArgumentException { + DataType containingFieldDataType, BinaryReader reader) throws IllegalArgumentException { StructureMappingInfo smi = getStructureMappingInfo(structureClass); if (smi == null) { throw new IllegalArgumentException( "Unknown structure mapped class: " + structureClass.getSimpleName()); } - return new StructureContext<>(this, smi, reader); + return new StructureContext<>(this, smi, containingFieldDataType, reader); + } + + /** + * Creates an artificial structure context to be used in some limited situations. + * + * @param type of structure mapped object + * @param structureClass class of structure mapped object + * @return new {@link StructureContext} + */ + public StructureContext createArtificialStructureContext(Class structureClass) { + StructureMappingInfo smi = getStructureMappingInfo(structureClass); + if (smi == null) { + throw new IllegalArgumentException( + "Unknown structure mapped class: " + structureClass.getSimpleName()); + } + return new StructureContext<>(this, smi, null); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMappingInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMappingInfo.java index d1a32d4774..d8cc90153a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMappingInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMappingInfo.java @@ -21,9 +21,9 @@ import java.lang.reflect.Method; import java.util.*; import ghidra.program.model.address.Address; -import ghidra.program.model.data.DataTypeComponent; -import ghidra.program.model.data.Structure; +import ghidra.program.model.data.*; import ghidra.program.model.listing.CodeUnit; +import ghidra.util.exception.CancelledException; /** * Immutable information needed to deserialize a field in a structure mapped class. @@ -205,11 +205,8 @@ public class FieldMappingInfo { Class fieldType = field.getType(); // TODO: be more strict about setter name, if specified it must be found or error - Method setterMethod = ReflectionHelper.findSetter(field.getName(), setterNameOverride, + this.setterMethod = ReflectionHelper.findSetter(field.getName(), setterNameOverride, structTargetClass, fieldType); - if (setterMethod != null) { - this.setterMethod = setterMethod; - } // setup the logic for deserializing the value from the in-memory structure if (fieldReadValueClass != FieldReadFunction.class) { @@ -274,13 +271,14 @@ public class FieldMappingInfo { } private Object readStructureMappedTypeFunc(FieldContext context) throws IOException { + DataType fieldDT = context.dtc() != null ? context.dtc().getDataType() : null; return context.structureContext() .getDataTypeMapper() - .readStructure(field.getType(), context.reader()); + .readStructure(field.getType(), fieldDT, context.reader()); } private void markupNestedStructure(FieldContext fieldContext, MarkupSession markupSession) - throws IOException { + throws IOException, CancelledException { markupSession.markup(fieldContext.getValue(Object.class), true); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMarkupFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMarkupFunction.java index 9fba1f1e4d..1077575e76 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMarkupFunction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMarkupFunction.java @@ -17,6 +17,8 @@ package ghidra.app.util.bin.format.golang.structmapping; import java.io.IOException; +import ghidra.util.exception.CancelledException; + /** * A function that decorates a field in a structure mapped class. * @@ -30,6 +32,8 @@ public interface FieldMarkupFunction { * @param fieldContext information about the field * @param markupSession state and methods to assist marking up the program * @throws IOException thrown if error performing the markup + * @throws CancelledException if cancelled */ - void markupField(FieldContext fieldContext, MarkupSession markupSession) throws IOException; + void markupField(FieldContext fieldContext, MarkupSession markupSession) + throws IOException, CancelledException; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/MarkupSession.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/MarkupSession.java index b5a5aac99b..e716c8432b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/MarkupSession.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/MarkupSession.java @@ -20,6 +20,7 @@ import java.lang.reflect.Array; import java.util.*; import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.app.util.bin.format.dwarf4.next.DWARFDataInstanceHelper; import ghidra.program.database.function.OverlappingFunctionException; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; @@ -29,7 +30,7 @@ import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.util.Msg; -import ghidra.util.exception.InvalidInputException; +import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; /** @@ -40,6 +41,7 @@ public class MarkupSession { protected Program program; protected DataTypeMapper mappingContext; protected Set

    markedupStructs = new HashSet<>(); + protected AddressSet markedupAddrs = new AddressSet(); protected TaskMonitor monitor; /** @@ -72,6 +74,10 @@ public class MarkupSession { return mappingContext; } + public AddressSet getMarkedupAddresses() { + return markedupAddrs; + } + /** * Decorates the specified object's memory using the various structure mapping tags that * were applied the object's class definition. @@ -85,12 +91,11 @@ public class MarkupSession { * who's data type has already been laid down in memory, removing the need for this object's * data type to be applied to memory * @throws IOException if error or cancelled + * @throws CancelledException if cancelled * @throws IllegalArgumentException if object instance is not a supported type */ - public void markup(T obj, boolean nested) throws IOException { - if (monitor.isCancelled()) { - throw new IOException("Markup canceled"); - } + public void markup(T obj, boolean nested) throws IOException, CancelledException { + monitor.checkCancelled(); if (obj == null) { return; } @@ -112,11 +117,12 @@ public class MarkupSession { } } else { - StructureContext structureContext = mappingContext.getStructureContextOfInstance(obj); + StructureContext structureContext = + mappingContext.getStructureContextOfInstance(obj); if (structureContext == null) { throw new IllegalArgumentException(); } - monitor.incrementProgress(1); + monitor.increment(); markupStructure(structureContext, nested); } } @@ -141,14 +147,19 @@ public class MarkupSession { * @throws IOException if error marking up address */ public void markupAddress(Address addr, DataType dt, int length) throws IOException { - try { - DataUtilities.createData(program, addr, dt, length, false, - ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA); - } - catch (CodeUnitInsertionException e) { - throw new IOException(e); - } + DWARFDataInstanceHelper dihUtil = + new DWARFDataInstanceHelper(program).setAllowTruncating(false); + if (dihUtil.isDataTypeCompatibleWithAddress(dt, addr)) { + try { + Data data = DataUtilities.createData(program, addr, dt, length, false, + ClearDataMode.CLEAR_ALL_CONFLICT_DATA); + markedupAddrs.add(data.getMinAddress(), data.getMaxAddress()); + } + catch (CodeUnitInsertionException e) { + throw new IOException(e); + } + } } /** @@ -171,11 +182,13 @@ public class MarkupSession { * @param structure mapped object type * @param obj structure mapped object * @param symbolName name + * @param namespaceName name of namespace to place the label symbol in, or null if root * @throws IOException if error */ - public void labelStructure(T obj, String symbolName) throws IOException { + public void labelStructure(T obj, String symbolName, String namespaceName) + throws IOException { Address addr = mappingContext.getAddressOfStructure(obj); - labelAddress(addr, symbolName); + labelAddress(addr, symbolName, namespaceName); } /** @@ -186,13 +199,35 @@ public class MarkupSession { * @throws IOException if error */ public void labelAddress(Address addr, String symbolName) throws IOException { + labelAddress(addr, symbolName, null); + } + + /** + * Places a label at the specified address. + * + * @param addr {@link Address} + * @param symbolName name + * @param namespaceName name of namespace to place the label symbol in, or null if root + * @throws IOException if error + */ + public void labelAddress(Address addr, String symbolName, String namespaceName) + throws IOException { try { SymbolTable symbolTable = program.getSymbolTable(); - Symbol[] symbols = symbolTable.getSymbols(addr); - if (symbols.length == 0 || symbols[0].isDynamic()) { - symbolName = SymbolUtilities.replaceInvalidChars(symbolName, true); - symbolTable.createLabel(addr, symbolName, SourceType.IMPORTED); + Namespace ns = null; + try { + ns = (namespaceName == null || namespaceName.isBlank()) + ? program.getGlobalNamespace() + : program.getSymbolTable() + .getOrCreateNameSpace(program.getGlobalNamespace(), namespaceName, + SourceType.IMPORTED); } + catch (DuplicateNameException e) { + ns = program.getGlobalNamespace(); + } + symbolName = SymbolUtilities.replaceInvalidChars(symbolName, true); + Symbol newLabelSym = symbolTable.createLabel(addr, symbolName, ns, SourceType.IMPORTED); + newLabelSym.setPrimary(); } catch (InvalidInputException e) { throw new IOException(e); @@ -237,6 +272,13 @@ public class MarkupSession { prefix, comment, sep); } + public void appendComment(Function func, String prefix, String comment) { + if (func != null) { + DWARFUtil.appendComment(program, func.getEntryPoint(), CodeUnit.PLATE_COMMENT, prefix, + comment, "\n"); + } + } + /** * Decorates a structure mapped structure, and everything it contains. * @@ -245,9 +287,10 @@ public class MarkupSession { * @param nested if true, it is assumed that the Ghidra data types have already been * placed and only markup needs to be performed. * @throws IOException if error marking up structure + * @throws CancelledException if cancelled */ public void markupStructure(StructureContext structureContext, boolean nested) - throws IOException { + throws IOException, CancelledException { Address addr = structureContext.getStructureAddress(); if (!nested && !markedupStructs.add(addr)) { return; @@ -257,6 +300,9 @@ public class MarkupSession { if (!nested) { try { Structure structDT = structureContext.getStructureDataType(); + if (structDT.isDeleted()) { + throw new IOException("Structure mapping data type invalid: " + structDT); + } markupAddress(addr, structDT); } catch (IOException e) { @@ -268,8 +314,9 @@ public class MarkupSession { if (instance instanceof StructureMarkup sm) { String structureLabel = sm.getStructureLabel(); + String namespaceName = sm.getStructureNamespace(); if (structureLabel != null && !structureLabel.isBlank()) { - labelAddress(addr, structureLabel); + labelAddress(addr, structureLabel, namespaceName); } } } @@ -282,7 +329,8 @@ public class MarkupSession { } - void markupFields(StructureContext structureContext) throws IOException { + void markupFields(StructureContext structureContext) + throws IOException, CancelledException { T structureInstance = structureContext.getStructureInstance(); StructureMappingInfo mappingInfo = structureContext.getMappingInfo(); for (FieldMappingInfo fmi : mappingInfo.getFields()) { @@ -330,31 +378,36 @@ public class MarkupSession { * Creates a default function at the specified address. * * @param name name of the new function + * @param ns namespace function should be in * @param addr address of the new function * @return {@link Function} that was created */ - public Function createFunctionIfMissing(String name, Address addr) { + public Function createFunctionIfMissing(String name, Namespace ns, Address addr) { Function function = program.getListing().getFunctionAt(addr); if (function == null) { - try { - if (!program.getMemory() - .getLoadedAndInitializedAddressSet() - .contains(addr)) { - Msg.warn(this, - "Unable to create function not contained within loaded memory: %s@%s" - .formatted(name, addr)); - return null; - } - function = program.getFunctionManager() - .createFunction(name, addr, new AddressSet(addr), SourceType.IMPORTED); + if (!program.getMemory().getLoadedAndInitializedAddressSet().contains(addr)) { + Msg.warn(this, "Unable to create function not contained within loaded memory: %s@%s" + .formatted(name, addr)); + return null; } - catch (OverlappingFunctionException | InvalidInputException e) { + try { + function = program.getFunctionManager() + .createFunction(name, ns, addr, new AddressSet(addr), SourceType.IMPORTED); + } + catch (OverlappingFunctionException | InvalidInputException + | IllegalArgumentException e) { Msg.error(this, e); + return null; } } else { - // TODO: this does nothing. re-evalulate this logic - //mappingContext.labelAddress(addr, name); + try { + function.setName(name, SourceType.IMPORTED); + function.setParentNamespace(ns); + } + catch (InvalidInputException | DuplicateNameException | CircularDependencyException e) { + Msg.error(this, e); + } } return function; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureContext.java index 429c1aed02..ece4be2d8f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureContext.java @@ -19,8 +19,7 @@ import java.io.IOException; import ghidra.app.util.bin.BinaryReader; import ghidra.program.model.address.Address; -import ghidra.program.model.data.DataTypeComponent; -import ghidra.program.model.data.Structure; +import ghidra.program.model.data.*; /** * Information about an instance of a structure that has been read from the memory of a @@ -45,6 +44,7 @@ import ghidra.program.model.data.Structure; public class StructureContext { protected final DataTypeMapper dataTypeMapper; protected final StructureMappingInfo mappingInfo; + protected final DataType containingFieldDataType; protected final BinaryReader reader; protected final long structureStart; protected T structureInstance; @@ -55,14 +55,31 @@ public class StructureContext { * * @param dataTypeMapper mapping context for the program * @param mappingInfo mapping information about this structure - * @param reader {@link BinaryReader} positioned at the start of the structure to be read + * @param reader {@link BinaryReader} positioned at the start of the structure to be read, or + * null if this is a limited-use context object */ public StructureContext(DataTypeMapper dataTypeMapper, StructureMappingInfo mappingInfo, BinaryReader reader) { + this(dataTypeMapper, mappingInfo, null, reader); + } + + /** + * Creates an instance of a {@link StructureContext}. + * + * @param dataTypeMapper mapping context for the program + * @param mappingInfo mapping information about this structure + * @param containingFieldDataType optional, the DataType of the field that contained the + * instance being deserialized + * @param reader {@link BinaryReader} positioned at the start of the structure to be read, or + * null if this is a limited-use context object + */ + public StructureContext(DataTypeMapper dataTypeMapper, StructureMappingInfo mappingInfo, + DataType containingFieldDataType, BinaryReader reader) { this.dataTypeMapper = dataTypeMapper; this.mappingInfo = mappingInfo; + this.containingFieldDataType = containingFieldDataType; this.reader = reader; - this.structureStart = reader.getPointerIndex(); + this.structureStart = reader != null ? reader.getPointerIndex() : -1; this.structureDataType = mappingInfo.getStructureDataType(); } @@ -108,6 +125,22 @@ public class StructureContext { return dataTypeMapper; } + /** + * Returns the {@link DataType} of the field that this object instance was contained inside of, + * or null if this instance was not a field inside another structure. + *

    + * For instance, if a structure was being deserialized because it was a field inside + * another structure, the actual Ghidra data type of the field may be slightly different + * than the structure data type defined at the top of the structmapped + * class (ie. {@code @StructureMapping(structureName='struct')}. The containing field's + * data type could allow custom logic to enrich or modify this struct's behavior. + * + * @return {@link DataType} of the field that this object instance was contained inside of + */ + public DataType getContainingFieldDataType() { + return containingFieldDataType; + } + /** * Returns the address in the program of this structure instance. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkup.java index 257096e6cf..ec4818fa5c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkup.java @@ -18,6 +18,8 @@ package ghidra.app.util.bin.format.golang.structmapping; import java.io.IOException; import java.util.List; +import ghidra.util.exception.CancelledException; + /** * Optional interface that structure mapped classes can implement that allows them to control how * their class is marked up. @@ -43,8 +45,13 @@ public interface StructureMarkup { /** * Returns a string that can be used to place a label on the instance. + *

    + * This default implementation will query the {@link #getStructureName()} method, and if + * it provides a value, will produce a string that looks like "name___mappingstructname", where + * "mappingstructname" will be the {@code structureName} value in the {@code @StructureMapping} + * annotation. * - * @return string to be used as a labe, or null if there is not a valid label for the instance + * @return string to be used as a label, or null if there is not a valid label for the instance * @throws IOException if error getting label */ default String getStructureLabel() throws IOException { @@ -55,13 +62,24 @@ public interface StructureMarkup { : null; } + /** + * Returns the namespace that any labels should be placed in. + * + * @return name of namespace to place the label for this structure mapped type, or null + * @throws IOException if error generating namespace name + */ + default String getStructureNamespace() throws IOException { + return null; + } + /** * Called to allow the implementor to perform custom markup of itself. * * @param session state and methods to assist marking up the program * @throws IOException if error during markup + * @throws CancelledException if cancelled */ - default void additionalMarkup(MarkupSession session) throws IOException { + default void additionalMarkup(MarkupSession session) throws IOException, CancelledException { // empty } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkupFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkupFunction.java index 93cda46eb0..612120613d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkupFunction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkupFunction.java @@ -17,6 +17,8 @@ package ghidra.app.util.bin.format.golang.structmapping; import java.io.IOException; +import ghidra.util.exception.CancelledException; + /** * Function that decorates a Ghidra structure * @@ -30,7 +32,8 @@ public interface StructureMarkupFunction { * @param context {@link StructureContext} * @param markupSession state and methods to assist marking up the program * @throws IOException thrown if error performing the markup + * @throws CancelledException */ void markupStructure(StructureContext context, MarkupSession markupSession) - throws IOException; + throws IOException, CancelledException; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java index a3e06f8aa2..b1380a81a1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java @@ -15,10 +15,9 @@ */ package ghidra.app.util.opinion; -import java.util.*; - import java.io.IOException; import java.io.InputStream; +import java.util.*; import com.google.common.primitives.Bytes; @@ -26,8 +25,10 @@ import ghidra.app.util.MemoryBlockUtils; import ghidra.app.util.Option; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.format.elf.info.ElfInfoItem.ItemWithAddress; import ghidra.app.util.bin.format.golang.GoBuildInfo; import ghidra.app.util.bin.format.golang.PEGoBuildId; +import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; import ghidra.app.util.bin.format.mz.DOSHeader; import ghidra.app.util.bin.format.pe.*; import ghidra.app.util.bin.format.pe.ImageCor20Header.ImageCor20Flags; @@ -141,7 +142,7 @@ public class PeLoader extends AbstractPeDebugLoader { processDelayImports(optionalHeader, program, monitor, log); processRelocations(optionalHeader, program, monitor, log); processDebug(optionalHeader, ntHeader, sectionToAddress, program, options, monitor); - processProperties(optionalHeader, program, monitor); + processProperties(optionalHeader, ntHeader, program, monitor); processComments(program.getListing(), monitor); processSymbols(ntHeader, sectionToAddress, program, monitor, log); @@ -275,7 +276,7 @@ public class PeLoader extends AbstractPeDebugLoader { } } - private void processProperties(OptionalHeader optionalHeader, Program prog, + private void processProperties(OptionalHeader optionalHeader, NTHeader ntHeader, Program prog, TaskMonitor monitor) { if (monitor.isCancelled()) { return; @@ -284,6 +285,24 @@ public class PeLoader extends AbstractPeDebugLoader { props.setInt("SectionAlignment", optionalHeader.getSectionAlignment()); props.setBoolean(RelocationTable.RELOCATABLE_PROP_NAME, prog.getRelocationTable().getSize() > 0); + + if (GoRttiMapper.isGolangProgram(prog)) { + processGolangProperties(optionalHeader, ntHeader, prog, monitor); + } + } + + private void processGolangProperties(OptionalHeader optionalHeader, NTHeader ntHeader, + Program prog, TaskMonitor monitor) { + + ItemWithAddress buildId = PEGoBuildId.findBuildId(prog); + if (buildId != null) { + buildId.item().markupProgram(prog, buildId.address()); + } + ItemWithAddress buildInfo = GoBuildInfo.findBuildInfo(prog); + if (buildInfo != null) { + buildInfo.item().markupProgram(prog, buildInfo.address()); + } + } private void processRelocations(OptionalHeader optionalHeader, Program prog, @@ -894,7 +913,7 @@ public class PeLoader extends AbstractPeDebugLoader { public final String label; // value stored as ProgramInformation.Compiler property public final String family; // used for Opinion secondary query param - private CompilerEnum(String label, String secondary) { + CompilerEnum(String label, String secondary) { this.label = label; this.family = secondary; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressAnnotatedStringHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressAnnotatedStringHandler.java index 1d2e3ed50c..65a7657314 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressAnnotatedStringHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressAnnotatedStringHandler.java @@ -35,6 +35,33 @@ public class AddressAnnotatedStringHandler implements AnnotatedStringHandler { "@address annotation must have an address" + "string"; private static final String[] SUPPORTED_ANNOTATIONS = { "address", "addr" }; + /** + * Constructs a well-formed Address Annotation comment string. + * + * @param destinationAddress destination of the annotation + * @param displayText text that will be used as the body of the annotation. Problematic + * characters will be escaped + * @return string + */ + public static String createAddressAnnotationString(Address destinationAddress, + String displayText) { + return "{@address %s %s}".formatted(destinationAddress.toString(false), + AnnotatedStringHandler.escapeAnnotationPart(displayText)); + } + + /** + * Constructs a well-formed Address Annotation comment string. + * + * @param addressOffset destination of the annotation + * @param displayText text that will be used as the body of the annotation. Problematic + * characters will be escaped + * @return string + */ + public static String createAddressAnnotationString(long addressOffset, String displayText) { + return "{@address 0x%x %s}".formatted(addressOffset, + AnnotatedStringHandler.escapeAnnotationPart(displayText)); + } + @Override public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, Program program) throws AnnotationException { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotatedStringHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotatedStringHandler.java index 12cc755a1b..94e176b6ee 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotatedStringHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AnnotatedStringHandler.java @@ -15,6 +15,11 @@ */ package ghidra.app.util.viewer.field; +import java.awt.event.MouseEvent; +import java.util.Objects; + +import docking.widgets.fieldpanel.field.AttributedString; +import docking.widgets.fieldpanel.field.FieldElement; import ghidra.app.nav.Navigatable; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.listing.Program; @@ -22,11 +27,6 @@ import ghidra.program.util.ProgramLocation; import ghidra.util.bean.field.AnnotatedTextFieldElement; import ghidra.util.classfinder.ExtensionPoint; -import java.awt.event.MouseEvent; - -import docking.widgets.fieldpanel.field.AttributedString; -import docking.widgets.fieldpanel.field.FieldElement; - /** * NOTE: ALL AnnotatedStringHandler CLASSES MUST END IN "StringHandler". If not, * the ClassSearcher will not find them. @@ -36,7 +36,27 @@ import docking.widgets.fieldpanel.field.FieldElement; */ public interface AnnotatedStringHandler extends ExtensionPoint { - public static final AnnotatedMouseHandler DUMMY_MOUSE_HANDLER = new AnnotatedMouseHandler() { + /** + * Escape a string that is intended to be used as a annotated string portion. + *

    + * Quotes are escaped, '}' and ' ' are placed inside quotes. + * + * @param s string to escape + * @return escaped string + */ + static String escapeAnnotationPart(String s) { + s = Objects.requireNonNullElse(s, ""); + String origStr = s; + if (s.indexOf('"') >= 0) { + s = s.replace("\"", "\\\""); + } + if (origStr != s || s.indexOf('}') != -1 || s.indexOf(' ') != -1) { + s = "\"" + s + "\""; + } + return s; + } + + AnnotatedMouseHandler DUMMY_MOUSE_HANDLER = new AnnotatedMouseHandler() { @Override public boolean handleMouseClick(ProgramLocation location, MouseEvent mouseEvent, ServiceProvider serviceProvider) { @@ -60,7 +80,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint { * @throws AnnotationException if the given text data does not fit the expected format for * the given handler implementation. */ - public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, + AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, Program program) throws AnnotationException; /** @@ -69,7 +89,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint { * * @return the annotation string names that this AnnotatedStringHandler supports. */ - public String[] getSupportedAnnotations(); + String[] getSupportedAnnotations(); /** * A method that is notified when an annotation is clicked. Returns true if this annotation @@ -81,27 +101,28 @@ public interface AnnotatedStringHandler extends ExtensionPoint { * @return true if this annotation handles the click; return false if this annotation does * not do anything with the click. */ - public boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable, + boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable, ServiceProvider serviceProvider); /** * Returns the String that represents the GUI presence of this option * @return the String to display in GUI components. */ - public String getDisplayString(); + String getDisplayString(); /** * Returns an example string of how the annotation is used * @return the example of how this is used. */ - public String getPrototypeString(); + String getPrototypeString(); /** * Returns an example string of how the annotation is used * @param displayText The text that may be wrapped, cannot be null * @return the example of how this is used. */ - public default String getPrototypeString(String displayText) { + default String getPrototypeString(String displayText) { return getPrototypeString(); } + } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/golang/rtti/GoSymbolNameTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/golang/rtti/GoSymbolNameTest.java new file mode 100644 index 0000000000..374a0c6fc3 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/golang/rtti/GoSymbolNameTest.java @@ -0,0 +1,103 @@ +/* ### + * IP: GHIDRA + * + * 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 + * + * http://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. + */ +package ghidra.app.util.bin.format.golang.rtti; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class GoSymbolNameTest { + @Test + public void testParse() { + GoSymbolName sni = GoSymbolName.parse("internal/fmtsort.(*SortedMap).Len"); + assertEquals("internal/fmtsort", sni.getPackagePath()); + assertEquals("fmtsort", sni.getPackageName()); + assertEquals("*SortedMap", sni.getRecieverString()); + + sni = GoSymbolName.parse("runtime..inittask"); + assertEquals("runtime", sni.getPackagePath()); + assertEquals("runtime", sni.getPackageName()); + assertNull(sni.getRecieverString()); + + sni = GoSymbolName.parse("runtime.addOneOpenDeferFrame.func1"); + assertEquals("runtime", sni.getPackagePath()); + assertEquals("runtime", sni.getPackageName()); + assertNull(sni.getRecieverString()); + + // test dots in the packagepath string + sni = GoSymbolName + .parse("vendor/golang.org/x/text/unicode/norm.(*reorderBuffer).compose"); + assertEquals("vendor/golang.org/x/text/unicode/norm", sni.getPackagePath()); + assertEquals("norm", sni.getPackageName()); + assertEquals("*reorderBuffer", sni.getRecieverString()); + + // test slashes/dots in the receiver string + sni = GoSymbolName.parse("sync/atomic.(*Pointer[net/http.response]).Store"); + assertEquals("sync/atomic", sni.getPackagePath()); + assertEquals("atomic", sni.getPackageName()); + assertEquals("*Pointer[net/http.response]", sni.getRecieverString()); + + sni = GoSymbolName.parse("crypto/ecdsa.inverse[go.shape.*uint8]"); + assertEquals("crypto/ecdsa", sni.getPackagePath()); + assertEquals("ecdsa", sni.getPackageName()); + assertNull(sni.getRecieverString()); + + sni = + GoSymbolName.parse("go:(*struct_{_runtime.gList;_runtime.n_int32_}).runtime.empty"); + assertNull(sni.getPackagePath()); + assertNull(sni.getPackageName()); + assertNull(sni.getRecieverString()); + + sni = GoSymbolName.parse("time.parseRFC3339[go.shape.[]uint8]"); + assertEquals("time", sni.getPackagePath()); + assertEquals("time", sni.getPackageName()); + assertNull(sni.getRecieverString()); + + sni = GoSymbolName.parse("sync/atomic.(*Pointer[interface_{}]).Load"); + assertEquals("sync/atomic", sni.getPackagePath()); + assertEquals("atomic", sni.getPackageName()); + assertEquals("*Pointer[interface_{}]", sni.getRecieverString()); + } + + @Test + public void testParseTypeSymbol() { + GoSymbolName sni = GoSymbolName.parse("type:.eq.runtime/internal/atomic.Int64"); + assertEquals("runtime/internal/atomic", sni.getPackagePath()); + assertEquals("atomic", sni.getPackageName()); + assertNull(sni.getRecieverString()); + + sni = GoSymbolName.parse("type:.eq.struct_{_runtime.gList;_runtime.n_int32_}"); + assertNull(sni.getPackagePath()); + assertNull(sni.getPackageName()); + assertNull(sni.getRecieverString()); + + sni = GoSymbolName.parse("type:.eq.sync/atomic.Pointer[interface_{}]"); + assertEquals("sync/atomic", sni.getPackagePath()); + assertEquals("atomic", sni.getPackageName()); + assertNull(sni.getRecieverString()); + + sni = GoSymbolName.parse("type:.eq.[...]internal/cpu.option"); + assertEquals("internal/cpu", sni.getPackagePath()); + assertEquals("cpu", sni.getPackageName()); + assertNull(sni.getRecieverString()); + + sni = GoSymbolName.parse("type:.eq.[39]vendor/golang.org/x/sys/cpu.option"); + assertEquals("vendor/golang.org/x/sys/cpu", sni.getPackagePath()); + assertEquals("cpu", sni.getPackageName()); + assertNull(sni.getRecieverString()); + + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/analysis/GolangDuffFixupAnalyzer.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/analysis/GolangDuffFixupAnalyzer.java deleted file mode 100644 index ea724859a6..0000000000 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/analysis/GolangDuffFixupAnalyzer.java +++ /dev/null @@ -1,191 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 - * - * http://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. - */ -package ghidra.app.plugin.core.analysis; - -import java.util.*; -import java.util.stream.Collectors; - -import ghidra.app.cmd.comments.SetCommentCmd; -import ghidra.app.decompiler.DecompInterface; -import ghidra.app.decompiler.DecompileResults; -import ghidra.app.decompiler.parallel.DecompilerCallback; -import ghidra.app.decompiler.parallel.ParallelDecompiler; -import ghidra.app.services.*; -import ghidra.app.util.bin.format.golang.GoConstants; -import ghidra.app.util.importer.MessageLog; -import ghidra.program.model.address.*; -import ghidra.program.model.listing.*; -import ghidra.program.model.listing.Function.FunctionUpdateType; -import ghidra.program.model.pcode.HighFunction; -import ghidra.program.model.pcode.PcodeBlockBasic; -import ghidra.program.model.symbol.*; -import ghidra.program.util.FunctionUtility; -import ghidra.util.Msg; -import ghidra.util.exception.*; -import ghidra.util.task.TaskMonitor; - -public class GolangDuffFixupAnalyzer extends AbstractAnalyzer { - private final static String NAME = "Golang Duff Function Fixup"; - private final static String DESCRIPTION = """ - Propagates function signature information from the base runtime.duffcopy \ - and runtime.duffzero functions to the other entry points that were discovered \ - during analysis."""; - - private Program program; - private TaskMonitor monitor; - private MessageLog log; - - public GolangDuffFixupAnalyzer() { - super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); - setPriority(AnalysisPriority.FUNCTION_ANALYSIS.after()); - setDefaultEnablement(true); - } - - @Override - public boolean canAnalyze(Program program) { - return GoConstants.GOLANG_CSPEC_NAME.equals( - program.getCompilerSpec().getCompilerSpecDescription().getCompilerSpecName()); - } - - @Override - public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) - throws CancelledException { - this.program = program; - this.monitor = monitor; - this.log = log; - - Symbol duffzeroSym = SymbolUtilities.getUniqueSymbol(program, "runtime.duffzero"); - Function duffzeroFunc = duffzeroSym != null ? (Function) duffzeroSym.getObject() : null; - Symbol duffcopySym = SymbolUtilities.getUniqueSymbol(program, "runtime.duffcopy"); - Function duffcopyFunc = duffcopySym != null ? (Function) duffcopySym.getObject() : null; - - List funcs = new ArrayList<>(); - if (duffzeroFunc != null && duffzeroFunc.getCallingConvention() != null) { - funcs.add(duffzeroFunc); - } - if (duffcopyFunc != null && duffcopyFunc.getCallingConvention() != null) { - funcs.add(duffcopyFunc); - } - - if (funcs.isEmpty()) { - return true; - } - - Map map = getFunctionActualRanges(funcs); - - if (duffzeroFunc != null) { - updateDuffFuncs(duffzeroFunc, map.get(duffzeroFunc.getEntryPoint())); - } - if (duffcopyFunc != null) { - updateDuffFuncs(duffcopyFunc, map.get(duffcopyFunc.getEntryPoint())); - } - - return true; - } - - /** - * Copy details from the base duff function to any other unnamed functions that start within - * the base duff function's range. - * - * @param duffFunc base duff function - * @param duffFuncBody the addresses the base function occupies - */ - private void updateDuffFuncs(Function duffFunc, AddressSetView duffFuncBody) { - if (duffFunc == null || duffFuncBody == null) { - return; - } - String duffComment = program.getListing() - .getCodeUnitAt(duffFunc.getEntryPoint()) - .getComment(CodeUnit.PLATE_COMMENT); - for (FunctionIterator funcIt = - program.getFunctionManager().getFunctions(duffFuncBody, true); funcIt.hasNext();) { - Function func = funcIt.next(); - if (!FunctionUtility.isDefaultFunctionName(func)) { - continue; - } - try { - func.setName(duffFunc.getName() + "_" + func.getEntryPoint(), SourceType.ANALYSIS); - func.updateFunction(duffFunc.getCallingConventionName(), duffFunc.getReturn(), - Arrays.asList(duffFunc.getParameters()), - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); - if (duffComment != null && !duffComment.isBlank()) { - new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment) - .applyTo(program); - } - } - catch (DuplicateNameException | InvalidInputException e) { - log.appendMsg("Error updating duff functions"); - log.appendException(e); - } - } - } - - private void configureDecompiler(DecompInterface decompiler) { - decompiler.toggleCCode(false); //only need syntax tree - decompiler.toggleSyntaxTree(true); // Produce syntax tree - decompiler.setSimplificationStyle("normalize"); - } - - record HighFunctionAddresses(Address functionEntry, AddressSetView functionAddresses) {} - - /** - * Returns the addresses that a function occupies (as determined by the decompiler instead of - * the disassembler). - * - * @param funcs list of functions - * @return map of function entry point and addresses for that function - */ - private Map getFunctionActualRanges(List funcs) { - DecompilerCallback callback = - new DecompilerCallback<>(program, this::configureDecompiler) { - @Override - public HighFunctionAddresses process(DecompileResults results, TaskMonitor tMonitor) - throws Exception { - tMonitor.checkCancelled(); - if (results == null) { - return null; - } - Function func = results.getFunction(); - HighFunction highFunc = results.getHighFunction(); - if (func == null || highFunc == null) { - return null; - } - AddressSet funcAddrs = new AddressSet(); - for (PcodeBlockBasic bb : highFunc.getBasicBlocks()) { - funcAddrs.add(bb.getStart(), bb.getStop()); - } - return new HighFunctionAddresses(func.getEntryPoint(), funcAddrs); - } - }; - - try { - List funcAddresses = - ParallelDecompiler.decompileFunctions(callback, funcs, monitor); - Map results = funcAddresses.stream() - .collect( - Collectors.toMap(hfa -> hfa.functionEntry, hfa -> hfa.functionAddresses)); - return results; - } - catch (Exception e) { - Msg.error(this, "Error: could not decompile functions with ParallelDecompiler", e); - return Map.of(); - } - finally { - callback.dispose(); - } - } - -} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/analyzer/DexAnalysisState.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/analyzer/DexAnalysisState.java index abdd1fb5db..cae862eda4 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/analyzer/DexAnalysisState.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/analyzer/DexAnalysisState.java @@ -19,8 +19,9 @@ import java.io.IOException; import java.util.List; import java.util.TreeMap; -import ghidra.app.plugin.core.analysis.AnalysisState; -import ghidra.app.plugin.core.analysis.AnalysisStateInfo; +import ghidra.app.plugin.core.analysis.TransientProgramProperties; +import ghidra.app.plugin.core.analysis.TransientProgramProperties.PropertyValueSupplier; +import ghidra.app.plugin.core.analysis.TransientProgramProperties.SCOPE; import ghidra.file.formats.android.dex.DexHeaderFactory; import ghidra.file.formats.android.dex.format.*; import ghidra.file.formats.android.dex.util.DexUtil; @@ -31,10 +32,10 @@ import ghidra.util.SystemUtilities; /** * This class is used to cache the {@link DexHeader} which holds constant pool and method information. - * These do not change for a given .dex Program and can be shared (by this {@link AnalysisState}) - * between plug-ins that need to do analysis. + * These do not change for a given .dex Program and can be shared between plug-ins that need to + * do analysis. */ -final public class DexAnalysisState implements AnalysisState { +final public class DexAnalysisState { private Program program; private DexHeader header; // Collection of raw records parsed from .dex file @@ -100,33 +101,39 @@ final public class DexAnalysisState implements AnalysisState { } /** - * Return persistent DexAnalysisState which corresponds to the specified program instance. + * Return shared/persistent {@link DexAnalysisState} which corresponds to the + * specified program instance. + * * @param program is the specified program instance * @return DexAnalysisState for specified program instance * @throws IOException if there are problems during construction of the state object */ public synchronized static DexAnalysisState getState(Program program) throws IOException { - DexAnalysisState analysisState = - AnalysisStateInfo.getAnalysisState(program, DexAnalysisState.class); - if (analysisState == null) { - DexHeader dexHeader = DexHeaderFactory.getDexHeader(program); - analysisState = new DexAnalysisState(program, dexHeader); - AnalysisStateInfo.putAnalysisState(program, analysisState); - } - return analysisState; + return TransientProgramProperties.getProperty(program, DexAnalysisState.class, + SCOPE.PROGRAM, DexAnalysisState.class, () -> { + DexHeader dexHeader = DexHeaderFactory.getDexHeader(program); + return new DexAnalysisState(program, dexHeader); + }); } + /** + * Return shared/persistent {@link DexAnalysisState} which corresponds to the + * specified program instance. + * + * @param program is the specified program instance + * @param address address of the dex header + * @return DexAnalysisState for specified program instance + * @throws IOException if there are problems during construction of the state object + */ public static DexAnalysisState getState(Program program, Address address) throws IOException { - DexAnalysisState analysisState = - AnalysisStateInfo.getAnalysisState(program, DexAnalysisState.class); - if (SystemUtilities.isInDevelopmentMode()) { - analysisState = null; //always generate when in debug mode - } - if (analysisState == null) { + PropertyValueSupplier supplier = () -> { DexHeader dexHeader = DexHeaderFactory.getDexHeader(program, address); - analysisState = new DexAnalysisState(program, dexHeader); - AnalysisStateInfo.putAnalysisState(program, analysisState); - } - return analysisState; + return new DexAnalysisState(program, dexHeader); + }; + return !SystemUtilities.isInDevelopmentMode() + ? TransientProgramProperties.getProperty(program, DexAnalysisState.class, + SCOPE.PROGRAM, DexAnalysisState.class, supplier) + : supplier.get(); + } } diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/util/task/UnknownProgressWrappingTaskMonitor.java b/Ghidra/Framework/Gui/src/main/java/ghidra/util/task/UnknownProgressWrappingTaskMonitor.java index e25a9c16d3..b0ffa7604c 100644 --- a/Ghidra/Framework/Gui/src/main/java/ghidra/util/task/UnknownProgressWrappingTaskMonitor.java +++ b/Ghidra/Framework/Gui/src/main/java/ghidra/util/task/UnknownProgressWrappingTaskMonitor.java @@ -21,6 +21,10 @@ package ghidra.util.task; */ public class UnknownProgressWrappingTaskMonitor extends WrappingTaskMonitor { + public UnknownProgressWrappingTaskMonitor(TaskMonitor delegate) { + this(delegate, 0); + } + public UnknownProgressWrappingTaskMonitor(TaskMonitor delegate, long startMaximum) { super(delegate); delegate.setMaximum(startMaximum); @@ -44,7 +48,8 @@ public class UnknownProgressWrappingTaskMonitor extends WrappingTaskMonitor { long _75_percent = currentMaximum - (currentMaximum / 4); if (progress > _75_percent) { - delegate.setMaximum(Math.max(progress, currentMaximum + currentMaximum / 4)); + delegate.setMaximum( + Math.max(Math.max(progress, 4), currentMaximum + currentMaximum / 4)); } } diff --git a/Ghidra/Framework/Gui/src/test/java/ghidra/util/task/UnknownProgressWrappingTaskMonitorTest.java b/Ghidra/Framework/Gui/src/test/java/ghidra/util/task/UnknownProgressWrappingTaskMonitorTest.java index ecb8c564cd..6925291633 100644 --- a/Ghidra/Framework/Gui/src/test/java/ghidra/util/task/UnknownProgressWrappingTaskMonitorTest.java +++ b/Ghidra/Framework/Gui/src/test/java/ghidra/util/task/UnknownProgressWrappingTaskMonitorTest.java @@ -48,4 +48,53 @@ public class UnknownProgressWrappingTaskMonitorTest extends AbstractGenericTest } + @Test + public void testUPWTM_startAtZero() throws CancelledException { + UnknownProgressWrappingTaskMonitor upwtm = + new UnknownProgressWrappingTaskMonitor(new TaskMonitorAdapter(true) { + long max; + long progress; + + @Override + public long getMaximum() { + return max; + } + + @Override + public void setMaximum(long max) { + this.max = max; + } + + @Override + public void initialize(long max) { + this.max = max; + progress = 0; + } + + @Override + public void setProgress(long value) { + progress = value; + } + + @Override + public void incrementProgress(long incrementAmount) { + progress += incrementAmount; + } + + @Override + public long getProgress() { + return progress; + } + }); + upwtm.initialize(0, "message"); + assertEquals(0, upwtm.getProgress()); + assertEquals(0, upwtm.getMaximum()); + + while (upwtm.getProgress() < 16) { + upwtm.increment(); + assertTrue(upwtm.getMaximum() > upwtm.getProgress()); + } + + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringDataInstance.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringDataInstance.java index cb21172b1b..8f13f1aa1b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringDataInstance.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringDataInstance.java @@ -205,6 +205,37 @@ public class StringDataInstance { return NULL_INSTANCE; } + /** + * Formats a string value so that it is in the form of a symbol label. + * + * @param prefixStr data type prefix, see {@link AbstractStringDataType#getDefaultLabelPrefix()} + * @param str string value + * @param options display options + * @return string, suitable to be used as a label + */ + public static String makeStringLabel(String prefixStr, String str, + DataTypeDisplayOptions options) { + boolean needsUnderscore = false; + StringBuilder buffer = new StringBuilder(); + for (int i = 0, strLength = str.length(); i < strLength && + buffer.length() < options.getLabelStringLength();) { + int codePoint = str.codePointAt(i); + if (StringUtilities.isDisplayable(codePoint) && (codePoint != ' ')) { + if (needsUnderscore) { + buffer.append('_'); + needsUnderscore = false; + } + buffer.appendCodePoint(codePoint); + } + else { + needsUnderscore = true; + // discard character + } + i += Character.charCount(codePoint); + } + return prefixStr + buffer.toString(); + } + //----------------------------------------------------------------------------- /** * A {@link StringDataInstance} that represents a non-existent string. @@ -1017,25 +1048,7 @@ public class StringDataInstance { return prefixStr; } - boolean needsUnderscore = false; - StringBuilder buffer = new StringBuilder(); - for (int i = 0, strLength = str.length(); i < strLength && - buffer.length() < options.getLabelStringLength();) { - int codePoint = str.codePointAt(i); - if (StringUtilities.isDisplayable(codePoint) && (codePoint != ' ')) { - if (needsUnderscore) { - buffer.append('_'); - needsUnderscore = false; - } - buffer.appendCodePoint(codePoint); - } - else { - needsUnderscore = true; - // discard character - } - i += Character.charCount(codePoint); - } - return prefixStr + buffer.toString(); + return makeStringLabel(prefixStr, str, options); } public String getOffcutLabelString(String prefixStr, String abbrevPrefixStr, String defaultStr, diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/format/ClassFileAnalysisState.java b/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/format/ClassFileAnalysisState.java index a9420e794f..12da6a2217 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/format/ClassFileAnalysisState.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/format/ClassFileAnalysisState.java @@ -18,8 +18,8 @@ package ghidra.javaclass.format; import java.io.IOException; import java.util.HashMap; -import ghidra.app.plugin.core.analysis.AnalysisState; -import ghidra.app.plugin.core.analysis.AnalysisStateInfo; +import ghidra.app.plugin.core.analysis.TransientProgramProperties; +import ghidra.app.plugin.core.analysis.TransientProgramProperties.SCOPE; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.MemoryByteProvider; import ghidra.program.model.address.*; @@ -32,10 +32,10 @@ import ghidra.util.Msg; * Class for holding the {@link ClassFileJava} and {@link MethodInfoJava} in memory * for a particular .class file Program. These describe the objects in the constant pool and * signatures of individual methods. They are parsed directly from the .class - * file (and so can't really change) and are shared via this {@link AnalysisState} with - * any plug-in that needs to do p-code analysis. + * file (and so can't really change) and are shared with any plug-in that needs to do + * p-code analysis. */ -public class ClassFileAnalysisState implements AnalysisState { +public class ClassFileAnalysisState { private Program program; private ClassFileJava classFile; // Constant-pool and method descriptions @@ -55,7 +55,7 @@ public class ClassFileAnalysisState implements AnalysisState { } /** - * @return the class file information {@link ClassFileJava} held by this {@link AnalysisState} + * @return the class file information {@link ClassFileJava} */ public ClassFileJava getClassFile() { return classFile; @@ -100,17 +100,15 @@ public class ClassFileAnalysisState implements AnalysisState { } /** - * Return persistent ClassFileAnalysisState which corresponds to the specified program instance. - * @param program - * @return ClassFileAnalysisState for specified program instance + * Return persistent ClassFileAnalysisState which corresponds to the specified + * program instance. + * + * @param program {@link Program} + * @return shared/persistent {@link ClassFileAnalysisState} for specified program instance + * @throws IOException if error reading java class file metadata */ public static synchronized ClassFileAnalysisState getState(Program program) throws IOException { - ClassFileAnalysisState analysisState = - AnalysisStateInfo.getAnalysisState(program, ClassFileAnalysisState.class); - if (analysisState == null) { - analysisState = new ClassFileAnalysisState(program); - AnalysisStateInfo.putAnalysisState(program, analysisState); - } - return analysisState; + return TransientProgramProperties.getProperty(program, ClassFileAnalysisState.class, + SCOPE.PROGRAM, ClassFileAnalysisState.class, () -> new ClassFileAnalysisState(program)); } }