Skip to content

Commit

Permalink
feat: add ability to query symbols via ipswd API routes `/api/syms/…
Browse files Browse the repository at this point in the history
…:uuid` and `/api/syms/:uuid/:addr`
  • Loading branch information
blacktop committed Jul 3, 2024
1 parent aae88ad commit 9f56962
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 16 deletions.
79 changes: 79 additions & 0 deletions api/server/routes/syms/syms.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@
package syms

import (
"errors"
"net/http"
"path/filepath"

"github.com/blacktop/ipsw/api/types"
"github.com/blacktop/ipsw/internal/db"
"github.com/blacktop/ipsw/internal/model"
"github.com/blacktop/ipsw/internal/syms"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
)

// swagger:response
type successResponse struct {
Success bool `json:"success,omitempty"`
}

// swagger:response
type symResponse *model.Symbol

// swagger:response
type symsResponse []*model.Symbol

// AddRoutes adds the syms routes to the router
func AddRoutes(rg *gin.RouterGroup, db db.Database) {
// swagger:route POST /syms/scan Syms postScan
Expand Down Expand Up @@ -45,4 +54,74 @@ func AddRoutes(rg *gin.RouterGroup, db db.Database) {
}
c.JSON(http.StatusOK, successResponse{Success: true})
})
// swagger:route GET /syms/{uuid} Syms getSymbols
//
// Symbols
//
// Get symbols for a given uuid.
//
// Produces:
// - application/json
//
// Parameters:
// + name: uuid
// in: path
// description: file UUID
// required: true
// type: string
//
// Responses:
// 200: symsResponse
// 500: genericError
rg.GET("/syms/:uuid", func(c *gin.Context) {
uuid := c.Param("uuid")
syms, err := syms.Get(uuid, db)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
c.AbortWithStatusJSON(http.StatusNotFound, types.GenericError{Error: err.Error()})
return
}
c.AbortWithStatusJSON(http.StatusInternalServerError, types.GenericError{Error: err.Error()})
return
}
c.JSON(http.StatusOK, symsResponse(syms))
})
// swagger:route GET /syms/{uuid}/{addr} Syms getSymbol
//
// Symbol
//
// Get symbols for a given uuid.
//
// Produces:
// - application/json
//
// Parameters:
// + name: uuid
// in: path
// description: file UUID
// required: true
// type: string
// + name: addr
// in: path
// description: symbol address
// required: true
// type: integer
//
// Responses:
// 200: symResponse
// 500: genericError
rg.GET("/syms/:uuid/:addr", func(c *gin.Context) {
uuid := c.Param("uuid")
addr := c.Param("addr")
sym, err := syms.GetForAddr(uuid, cast.ToUint64(addr), db)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
c.AbortWithStatusJSON(http.StatusNotFound, types.GenericError{Error: err.Error()})
return
}
c.AbortWithStatusJSON(http.StatusInternalServerError, types.GenericError{Error: err.Error()})
return
}
c.JSON(http.StatusOK, symResponse(sym))
})
}
82 changes: 82 additions & 0 deletions api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,73 @@
}
}
},
"/syms/{uuid}": {
"get": {
"description": "Get symbols for a given uuid.",
"produces": [
"application/json"
],
"tags": [
"Syms"
],
"summary": "Symbols",
"operationId": "getSymbols",
"parameters": [
{
"type": "string",
"description": "file UUID",
"name": "uuid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/symsResponse"
},
"500": {
"$ref": "#/responses/genericError"
}
}
}
},
"/syms/{uuid}/{addr}": {
"get": {
"description": "Get symbols for a given uuid.",
"produces": [
"application/json"
],
"tags": [
"Syms"
],
"summary": "Symbol",
"operationId": "getSymbol",
"parameters": [
{
"type": "string",
"description": "file UUID",
"name": "uuid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "symbol address",
"name": "addr",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/symResponse"
},
"500": {
"$ref": "#/responses/genericError"
}
}
}
},
"/unmount": {
"post": {
"description": "Unmount a previously mounted DMG.",
Expand Down Expand Up @@ -3823,6 +3890,21 @@
}
}
},
"symResponse": {
"description": "",
"schema": {
"$ref": "#/definitions/Symbol"
}
},
"symsResponse": {
"description": "",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
}
},
"versionResponse": {
"description": "",
"headers": {
Expand Down
5 changes: 4 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func (c *Config) verify() error {
if err != nil {
return fmt.Errorf("config: failed to get user home directory: %v", err)
}
// verify daemon
if c.Daemon.Host == "" && c.Daemon.Port == 0 && c.Daemon.Socket == "" {
if os.Getenv("IPSW_IN_SNAP") == "1" {
c.Daemon.Socket = "/var/snap/ipswd/common/ipsw.sock"
Expand All @@ -57,7 +58,9 @@ func (c *Config) verify() error {
c.Daemon.Host = "localhost"
} else if strings.HasPrefix(c.Daemon.Socket, "~/") {
c.Daemon.Socket = filepath.Join(home, c.Daemon.Socket[2:]) // TODO: is this bad practice?
} else if c.Database.BatchSize == 0 {
}
// verify database
if c.Database.BatchSize == 0 {
c.Database.BatchSize = 1000
}

Expand Down
4 changes: 4 additions & 0 deletions internal/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ type Database interface {
// It returns ErrNotFound if the key does not exist.
GetByName(name string) (*model.Ipsw, error)

GetSymbol(uuid string, addr uint64) (*model.Symbol, error)

GetSymbols(uuid string) ([]*model.Symbol, error)

// Save updates the IPSW.
// It overwrites any previous value for that IPSW.
Save(value any) error
Expand Down
62 changes: 62 additions & 0 deletions internal/db/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,68 @@ func (m *Memory) GetByName(name string) (*model.Ipsw, error) {
return nil, model.ErrNotFound
}

func (m *Memory) GetSymbol(uuid string, addr uint64) (*model.Symbol, error) {
for _, ipsw := range m.IPSWs {
for _, dyld := range ipsw.DSCs {
for _, img := range dyld.Images {
if img.UUID == uuid {
for _, sym := range img.Symbols {
if addr >= sym.Start && addr < sym.End {
return sym, nil
}
}
}
}
}
for _, fs := range ipsw.FileSystem {
if fs.UUID == uuid {
for _, sym := range fs.Symbols {
if addr >= sym.Start && addr < sym.End {
return sym, nil
}
}
}
}
for _, fs := range ipsw.Kernels {
for _, kext := range fs.Kexts {
if fs.UUID == uuid {
for _, sym := range kext.Symbols {
if addr >= sym.Start && addr < sym.End {
return sym, nil
}
}
}
}
}
}
return nil, model.ErrNotFound
}

func (m *Memory) GetSymbols(uuid string) ([]*model.Symbol, error) {
for _, ipsw := range m.IPSWs {
for _, dyld := range ipsw.DSCs {
for _, img := range dyld.Images {
if img.UUID == uuid {
return img.Symbols, nil
}
}
}
for _, fs := range ipsw.FileSystem {
if fs.UUID == uuid {
return fs.Symbols, nil
}
}
for _, fs := range ipsw.Kernels {
for _, kext := range fs.Kexts {
if fs.UUID == uuid {
return kext.Symbols, nil
}
}
}
}
return nil, model.ErrNotFound
}

// Set sets the value for the given key.
// It overwrites any previous value for that key.
func (m *Memory) Save(value any) error {
Expand Down
30 changes: 30 additions & 0 deletions internal/db/postgres.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package db

import (
"errors"
"fmt"

"github.com/blacktop/ipsw/internal/model"
Expand Down Expand Up @@ -91,6 +92,35 @@ func (p *Postgres) GetByName(name string) (*model.Ipsw, error) {
return i, nil
}

func (p *Postgres) GetSymbol(uuid string, address uint64) (*model.Symbol, error) {
var symbol model.Symbol
if err := p.db.Joins("JOIN macho_syms ON macho_syms.symbol_id = symbols.id").
Joins("JOIN machos ON machos.uuid = macho_syms.macho_uuid").
Where("machos.uuid = ? AND symbols.start <= ? AND ? < symbols.end", uuid, address, address).
First(&symbol).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, model.ErrNotFound
}
return nil, err
}
return &symbol, nil
}

func (p *Postgres) GetSymbols(uuid string) ([]*model.Symbol, error) {
var syms []*model.Symbol
if err := p.db.Joins("JOIN macho_syms ON macho_syms.symbol_id = symbols.id").
Joins("JOIN machos ON machos.uuid = macho_syms.macho_uuid").
Where("machos.uuid = ?", uuid).
Select("symbol", "start", "end").
Find(&syms).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, model.ErrNotFound
}
return nil, err
}
return syms, nil
}

// Set sets the value for the given key.
// It overwrites any previous value for that key.
func (p *Postgres) Save(value any) error {
Expand Down
28 changes: 28 additions & 0 deletions internal/db/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,34 @@ func (s *Sqlite) GetByName(name string) (*model.Ipsw, error) {
return i, nil
}

func (s *Sqlite) GetSymbol(uuid string, address uint64) (*model.Symbol, error) {
var symbol model.Symbol
if err := s.db.Joins("JOIN macho_syms ON macho_syms.symbol_id = symbols.id").
Joins("JOIN machos ON machos.uuid = macho_syms.macho_uuid").
Where("machos.uuid = ? AND symbols.start <= ? AND ? < symbols.end", uuid, address, address).
First(&symbol).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, model.ErrNotFound
}
return nil, err
}
return &symbol, nil
}

func (s *Sqlite) GetSymbols(uuid string) ([]*model.Symbol, error) {
var syms []*model.Symbol
if err := s.db.Joins("JOIN macho_syms ON macho_syms.symbol_id = symbols.id").
Joins("JOIN machos ON machos.uuid = macho_syms.macho_uuid").
Where("machos.uuid = ?", uuid).
Find(syms).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, model.ErrNotFound
}
return nil, err
}
return syms, nil
}

// Set sets the value for the given key.
// It overwrites any previous value for that key.
func (s *Sqlite) Save(value any) error {
Expand Down
Loading

0 comments on commit 9f56962

Please sign in to comment.