platform: Add
Crash Rerport
This commit is contained in:
@@ -540,6 +540,24 @@ func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerGoCrash() error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerDebugCrash(context.Background(), &daemon.DebugCrashRequest{
|
||||
Type: daemon.DebugCrashRequest_GO,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerNativeCrash() error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerDebugCrash(context.Background(), &daemon.DebugCrashRequest{
|
||||
Type: daemon.DebugCrashRequest_NATIVE,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
||||
return callWithResult(c, func(client daemon.StartedServiceClient) (DeprecatedNoteIterator, error) {
|
||||
warnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{})
|
||||
|
||||
@@ -39,6 +39,7 @@ type CommandServerHandler interface {
|
||||
ServiceReload() error
|
||||
GetSystemProxyStatus() (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
TriggerNativeCrash() error
|
||||
WriteDebugMessage(message string)
|
||||
}
|
||||
|
||||
@@ -170,11 +171,16 @@ type OverrideOptions struct {
|
||||
}
|
||||
|
||||
func (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error {
|
||||
return s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{
|
||||
err := s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{
|
||||
AutoRedirect: options.AutoRedirect,
|
||||
IncludePackage: iteratorToArray(options.IncludePackage),
|
||||
ExcludePackage: iteratorToArray(options.ExcludePackage),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
saveConfigSnapshot(configContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) CloseService() error {
|
||||
@@ -271,6 +277,10 @@ func (h *platformHandler) SetSystemProxyEnabled(enabled bool) error {
|
||||
return (*CommandServer)(h).handler.SetSystemProxyEnabled(enabled)
|
||||
}
|
||||
|
||||
func (h *platformHandler) TriggerNativeCrash() error {
|
||||
return (*CommandServer)(h).handler.TriggerNativeCrash()
|
||||
}
|
||||
|
||||
func (h *platformHandler) WriteDebugMessage(message string) {
|
||||
(*CommandServer)(h).handler.WriteDebugMessage(message)
|
||||
}
|
||||
|
||||
9
experimental/libbox/debug.go
Normal file
9
experimental/libbox/debug.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package libbox
|
||||
|
||||
import "time"
|
||||
|
||||
func TriggerGoPanic() {
|
||||
time.AfterFunc(200*time.Millisecond, func() {
|
||||
panic("debug go crash")
|
||||
})
|
||||
}
|
||||
@@ -3,17 +3,118 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
const (
|
||||
crashReportMetadataFileName = "metadata.json"
|
||||
crashReportGoLogFileName = "go.log"
|
||||
crashReportConfigFileName = "configuration.json"
|
||||
)
|
||||
|
||||
var crashOutputFile *os.File
|
||||
|
||||
func RedirectStderr(path string) error {
|
||||
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
|
||||
_ = os.Rename(path, path+".old")
|
||||
type crashReportMetadata struct {
|
||||
Source string `json:"source"`
|
||||
BundleIdentifier string `json:"bundleIdentifier,omitempty"`
|
||||
ProcessName string `json:"processName,omitempty"`
|
||||
ProcessPath string `json:"processPath,omitempty"`
|
||||
StartedAt string `json:"startedAt,omitempty"`
|
||||
CrashedAt string `json:"crashedAt,omitempty"`
|
||||
AppVersion string `json:"appVersion,omitempty"`
|
||||
AppMarketingVersion string `json:"appMarketingVersion,omitempty"`
|
||||
CoreVersion string `json:"coreVersion,omitempty"`
|
||||
GoVersion string `json:"goVersion,omitempty"`
|
||||
SignalName string `json:"signalName,omitempty"`
|
||||
SignalCode string `json:"signalCode,omitempty"`
|
||||
ExceptionName string `json:"exceptionName,omitempty"`
|
||||
ExceptionReason string `json:"exceptionReason,omitempty"`
|
||||
}
|
||||
|
||||
func archiveCrashReport(path string, crashReportsDir string) {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil || len(content) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
info, _ := os.Stat(path)
|
||||
crashTime := time.Now().UTC()
|
||||
if info != nil {
|
||||
crashTime = info.ModTime().UTC()
|
||||
}
|
||||
|
||||
metadata := currentCrashReportMetadata(crashTime)
|
||||
if len(bytes.TrimSpace(content)) == 0 {
|
||||
os.Remove(path)
|
||||
return
|
||||
}
|
||||
|
||||
os.MkdirAll(crashReportsDir, 0o777)
|
||||
destName := crashTime.Format("2006-01-02T15-04-05")
|
||||
destPath := filepath.Join(crashReportsDir, destName)
|
||||
for i := 1; ; i++ {
|
||||
if _, err := os.Stat(destPath); os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
destPath = filepath.Join(crashReportsDir,
|
||||
crashTime.Format("2006-01-02T15-04-05")+"-"+strconv.Itoa(i))
|
||||
}
|
||||
|
||||
os.MkdirAll(destPath, 0o777)
|
||||
logPath := filepath.Join(destPath, crashReportGoLogFileName)
|
||||
os.WriteFile(logPath, content, 0o666)
|
||||
if runtime.GOOS != "android" {
|
||||
os.Chown(destPath, sUserID, sGroupID)
|
||||
os.Chown(logPath, sUserID, sGroupID)
|
||||
}
|
||||
writeCrashReportMetadata(destPath, metadata)
|
||||
os.Remove(path)
|
||||
archiveConfigSnapshot(destPath)
|
||||
}
|
||||
|
||||
func configSnapshotPath() string {
|
||||
return filepath.Join(sTempPath, crashReportConfigFileName)
|
||||
}
|
||||
|
||||
func saveConfigSnapshot(configContent string) {
|
||||
snapshotPath := configSnapshotPath()
|
||||
os.WriteFile(snapshotPath, []byte(configContent), 0o666)
|
||||
if runtime.GOOS != "android" {
|
||||
os.Chown(snapshotPath, sUserID, sGroupID)
|
||||
}
|
||||
}
|
||||
|
||||
func archiveConfigSnapshot(destPath string) {
|
||||
snapshotPath := configSnapshotPath()
|
||||
content, err := os.ReadFile(snapshotPath)
|
||||
if err != nil || len(bytes.TrimSpace(content)) == 0 {
|
||||
return
|
||||
}
|
||||
configPath := filepath.Join(destPath, crashReportConfigFileName)
|
||||
os.WriteFile(configPath, content, 0o666)
|
||||
if runtime.GOOS != "android" {
|
||||
os.Chown(configPath, sUserID, sGroupID)
|
||||
}
|
||||
os.Remove(snapshotPath)
|
||||
}
|
||||
|
||||
func redirectStderr(path string) error {
|
||||
crashReportsDir := filepath.Join(sWorkingPath, "crash_reports")
|
||||
archiveCrashReport(path, crashReportsDir)
|
||||
archiveCrashReport(path+".old", crashReportsDir)
|
||||
|
||||
outputFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -26,6 +127,7 @@ func RedirectStderr(path string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = debug.SetCrashOutput(outputFile, debug.CrashOptions{})
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
@@ -35,3 +137,107 @@ func RedirectStderr(path string) error {
|
||||
crashOutputFile = outputFile
|
||||
return nil
|
||||
}
|
||||
|
||||
func currentCrashReportMetadata(crashTime time.Time) crashReportMetadata {
|
||||
processPath, _ := os.Executable()
|
||||
processName := filepath.Base(processPath)
|
||||
if processName == "." {
|
||||
processName = ""
|
||||
}
|
||||
return crashReportMetadata{
|
||||
Source: sCrashReportSource,
|
||||
ProcessName: processName,
|
||||
ProcessPath: processPath,
|
||||
CrashedAt: crashTime.Format(time.RFC3339),
|
||||
CoreVersion: C.Version,
|
||||
GoVersion: GoVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
func writeCrashReportMetadata(reportPath string, metadata crashReportMetadata) {
|
||||
data, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
metaPath := filepath.Join(reportPath, crashReportMetadataFileName)
|
||||
os.WriteFile(metaPath, data, 0o666)
|
||||
if runtime.GOOS != "android" {
|
||||
os.Chown(metaPath, sUserID, sGroupID)
|
||||
}
|
||||
}
|
||||
|
||||
func CreateZipArchive(sourcePath string, destinationPath string) error {
|
||||
sourceInfo, err := os.Stat(sourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sourceInfo.IsDir() {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
destinationFile, err := os.Create(destinationPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = destinationFile.Close()
|
||||
}()
|
||||
|
||||
zipWriter := zip.NewWriter(destinationFile)
|
||||
|
||||
rootName := filepath.Base(sourcePath)
|
||||
err = filepath.WalkDir(sourcePath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relativePath, err := filepath.Rel(sourcePath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if relativePath == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
archivePath := filepath.ToSlash(filepath.Join(rootName, relativePath))
|
||||
if d.IsDir() {
|
||||
_, err = zipWriter.Create(archivePath + "/")
|
||||
return err
|
||||
}
|
||||
|
||||
fileInfo, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := zip.FileInfoHeader(fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = archivePath
|
||||
header.Method = zip.Deflate
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(writer, sourceFile)
|
||||
closeErr := sourceFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return closeErr
|
||||
})
|
||||
if err != nil {
|
||||
_ = zipWriter.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return zipWriter.Close()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package libbox
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
@@ -22,6 +24,7 @@ var (
|
||||
sCommandServerSecret string
|
||||
sLogMaxLines int
|
||||
sDebug bool
|
||||
sCrashReportSource string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -38,6 +41,7 @@ type SetupOptions struct {
|
||||
CommandServerSecret string
|
||||
LogMaxLines int
|
||||
Debug bool
|
||||
CrashReportSource string
|
||||
}
|
||||
|
||||
func Setup(options *SetupOptions) error {
|
||||
@@ -56,10 +60,11 @@ func Setup(options *SetupOptions) error {
|
||||
sCommandServerSecret = options.CommandServerSecret
|
||||
sLogMaxLines = options.LogMaxLines
|
||||
sDebug = options.Debug
|
||||
sCrashReportSource = options.CrashReportSource
|
||||
|
||||
os.MkdirAll(sWorkingPath, 0o777)
|
||||
os.MkdirAll(sTempPath, 0o777)
|
||||
return nil
|
||||
return redirectStderr(filepath.Join(sTempPath, "CrashReport-"+sCrashReportSource+".log"))
|
||||
}
|
||||
|
||||
func SetLocale(localeId string) {
|
||||
@@ -70,6 +75,10 @@ func Version() string {
|
||||
return C.Version
|
||||
}
|
||||
|
||||
func GoVersion() string {
|
||||
return runtime.Version() + ", " + runtime.GOOS + "/" + runtime.GOARCH
|
||||
}
|
||||
|
||||
func FormatBytes(length int64) string {
|
||||
return byteformats.FormatKBytes(uint64(length))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user