github.com/ks888/tgo@v0.0.0-20190130135156-80bf89407292/tracee/process_test.go (about)

     1  package tracee
     2  
     3  import (
     4  	"debug/dwarf"
     5  	"os/exec"
     6  	"runtime"
     7  	"testing"
     8  
     9  	"github.com/ks888/tgo/testutils"
    10  	"golang.org/x/arch/x86/x86asm"
    11  )
    12  
    13  var helloworldAttr = Attributes{
    14  	FirstModuleDataAddr: testutils.HelloworldAddrFirstModuleData,
    15  	CompiledGoVersion:   runtime.Version(),
    16  }
    17  
    18  var infloopAttr = Attributes{
    19  	ProgramPath:         testutils.ProgramInfloop,
    20  	FirstModuleDataAddr: testutils.InfloopAddrFirstModuleData,
    21  	CompiledGoVersion:   runtime.Version(),
    22  }
    23  
    24  func TestLaunchProcess(t *testing.T) {
    25  	proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
    26  	if err != nil {
    27  		t.Fatalf("failed to launch process: %v", err)
    28  	}
    29  	defer proc.Detach()
    30  	if proc.debugapiClient == nil {
    31  		t.Errorf("debugapiClient is nil")
    32  	}
    33  }
    34  
    35  func TestAttachProcess(t *testing.T) {
    36  	cmd := exec.Command(testutils.ProgramInfloop)
    37  	_ = cmd.Start()
    38  
    39  	proc, err := AttachProcess(cmd.Process.Pid, infloopAttr)
    40  	if err != nil {
    41  		t.Fatalf("failed to attach process: %v", err)
    42  	}
    43  	if proc.debugapiClient == nil {
    44  		t.Errorf("debugapiClient is nil")
    45  	}
    46  	defer func() {
    47  		proc.Detach() // must detach before kill. Otherwise, the program becomes zombie.
    48  		cmd.Process.Kill()
    49  		cmd.Process.Wait()
    50  	}()
    51  }
    52  
    53  func TestDetach(t *testing.T) {
    54  	proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
    55  	if err != nil {
    56  		t.Fatalf("failed to launch process: %v", err)
    57  	}
    58  
    59  	if err := proc.SetBreakpoint(testutils.HelloworldAddrNoParameter); err != nil {
    60  		t.Fatalf("failed to set breakpoint: %v", err)
    61  	}
    62  
    63  	if err := proc.Detach(); err != nil {
    64  		t.Fatalf("failed to detach process: %v", err)
    65  	}
    66  
    67  	if proc.ExistBreakpoint(testutils.HelloworldAddrNoParameter) {
    68  		t.Errorf("breakpoint still exists")
    69  	}
    70  }
    71  
    72  func TestContinueAndWait(t *testing.T) {
    73  	proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
    74  	if err != nil {
    75  		t.Fatalf("failed to launch process: %v", err)
    76  	}
    77  	defer proc.Detach()
    78  
    79  	// 1. stop at NoParameter func
    80  	if err := proc.SetBreakpoint(testutils.HelloworldAddrNoParameter); err != nil {
    81  		t.Fatalf("failed to set breakpoint: %v", err)
    82  	}
    83  	event, err := proc.ContinueAndWait()
    84  	if err != nil {
    85  		t.Fatalf("failed to continue and wait: %v", err)
    86  	}
    87  	if err := proc.ClearBreakpoint(testutils.HelloworldAddrNoParameter); err != nil {
    88  		t.Fatalf("failed to set breakpoint: %v", err)
    89  	}
    90  	tids := event.Data.([]int)
    91  	if err := proc.setPC(tids[0], testutils.HelloworldAddrNoParameter); err != nil {
    92  		t.Fatalf("failed to set breakpoint: %v", err)
    93  	}
    94  
    95  	// 2. stop at OneParameter func
    96  	if err := proc.SetBreakpoint(testutils.HelloworldAddrOneParameter); err != nil {
    97  		t.Fatalf("failed to set breakpoint: %v", err)
    98  	}
    99  	if _, err := proc.ContinueAndWait(); err != nil {
   100  		t.Fatalf("failed to continue and wait: %v", err)
   101  	}
   102  	info, err := proc.CurrentGoRoutineInfo(tids[0])
   103  	if err != nil {
   104  		t.Fatalf("failed to get CurrentGoRoutineInfo: %v", err)
   105  	}
   106  	if info.CurrentPC-1 != testutils.HelloworldAddrOneParameter {
   107  		t.Errorf("stop at unexpected address: %x", info.CurrentPC)
   108  	}
   109  }
   110  
   111  func TestSingleStep(t *testing.T) {
   112  	proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
   113  	if err != nil {
   114  		t.Fatalf("failed to launch process: %v", err)
   115  	}
   116  	defer proc.Detach()
   117  
   118  	if err := proc.SetBreakpoint(testutils.HelloworldAddrNoParameter); err != nil {
   119  		t.Fatalf("failed to set breakpoint: %v", err)
   120  	}
   121  	event, err := proc.ContinueAndWait()
   122  	if err != nil {
   123  		t.Fatalf("failed to continue and wait: %v", err)
   124  	}
   125  
   126  	tids := event.Data.([]int)
   127  	if err := proc.SingleStep(tids[0], testutils.HelloworldAddrNoParameter); err != nil {
   128  		t.Fatalf("single-step failed: %v", err)
   129  	}
   130  	if !proc.ExistBreakpoint(testutils.HelloworldAddrNoParameter) {
   131  		t.Errorf("breakpoint is cleared")
   132  	}
   133  }
   134  
   135  func TestSingleStep_NoBreakpoint(t *testing.T) {
   136  	proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
   137  	if err != nil {
   138  		t.Fatalf("failed to launch process: %v", err)
   139  	}
   140  	defer proc.Detach()
   141  
   142  	if err := proc.SetBreakpoint(testutils.HelloworldAddrNoParameter); err != nil {
   143  		t.Fatalf("failed to set breakpoint: %v", err)
   144  	}
   145  	event, err := proc.ContinueAndWait()
   146  	if err != nil {
   147  		t.Fatalf("failed to continue and wait: %v", err)
   148  	}
   149  	if err := proc.ClearBreakpoint(testutils.HelloworldAddrNoParameter); err != nil {
   150  		t.Fatalf("failed to clear breakpoint: %v", err)
   151  	}
   152  
   153  	tids := event.Data.([]int)
   154  	if err := proc.SingleStep(tids[0], testutils.HelloworldAddrNoParameter); err != nil {
   155  		t.Fatalf("single-step failed: %v", err)
   156  	}
   157  	if proc.ExistBreakpoint(testutils.HelloworldAddrNoParameter) {
   158  		t.Errorf("breakpoint is set")
   159  	}
   160  }
   161  
   162  func TestStackFrameAt(t *testing.T) {
   163  	proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
   164  	if err != nil {
   165  		t.Fatalf("failed to launch process: %v", err)
   166  	}
   167  	defer proc.Detach()
   168  
   169  	if err := proc.SetBreakpoint(testutils.HelloworldAddrOneParameterAndVariable); err != nil {
   170  		t.Fatalf("failed to set breakpoint: %v", err)
   171  	}
   172  
   173  	event, err := proc.ContinueAndWait()
   174  	if err != nil {
   175  		t.Fatalf("failed to continue and wait: %v", err)
   176  	}
   177  
   178  	tids := event.Data.([]int)
   179  	regs, err := proc.debugapiClient.ReadRegisters(tids[0])
   180  	if err != nil {
   181  		t.Fatalf("failed to read registers: %v", err)
   182  	}
   183  
   184  	stackFrame, err := proc.StackFrameAt(regs.Rsp, regs.Rip)
   185  	if err != nil {
   186  		t.Fatalf("error: %v", err)
   187  	}
   188  	if stackFrame.Function.Name != "main.oneParameterAndOneVariable" {
   189  		t.Errorf("wrong function name: %s", stackFrame.Function.Name)
   190  	}
   191  	if stackFrame.Function.StartAddr != testutils.HelloworldAddrOneParameterAndVariable {
   192  		t.Errorf("start addr is 0")
   193  	}
   194  	if stackFrame.Function.EndAddr == 0 {
   195  		t.Errorf("end addr is 0")
   196  	}
   197  	if stackFrame.ReturnAddress == 0x0 {
   198  		t.Errorf("empty return address")
   199  	}
   200  	if len(stackFrame.InputArguments) != 1 {
   201  		t.Errorf("wrong input args length: %d", len(stackFrame.InputArguments))
   202  	}
   203  	if stackFrame.InputArguments[0].ParseValue(1) != "i = 1" {
   204  		t.Errorf("wrong input args: %s", stackFrame.InputArguments[0].ParseValue(1))
   205  	}
   206  	if len(stackFrame.OutputArguments) != 0 {
   207  		t.Errorf("wrong output args length: %d", len(stackFrame.OutputArguments))
   208  	}
   209  }
   210  
   211  func TestStackFrameAt_NoDwarfCase(t *testing.T) {
   212  	proc, err := LaunchProcess(testutils.ProgramHelloworldNoDwarf, nil, helloworldAttr)
   213  	if err != nil {
   214  		t.Fatalf("failed to launch process: %v", err)
   215  	}
   216  	defer proc.Detach()
   217  
   218  	if err := proc.SetBreakpoint(testutils.HelloworldAddrOneParameterAndVariable); err != nil {
   219  		t.Fatalf("failed to set breakpoint: %v", err)
   220  	}
   221  
   222  	event, err := proc.ContinueAndWait()
   223  	if err != nil {
   224  		t.Fatalf("failed to continue and wait: %v", err)
   225  	}
   226  
   227  	tids := event.Data.([]int)
   228  	regs, err := proc.debugapiClient.ReadRegisters(tids[0])
   229  	if err != nil {
   230  		t.Fatalf("failed to read registers: %v", err)
   231  	}
   232  
   233  	stackFrame, err := proc.StackFrameAt(regs.Rsp, regs.Rip)
   234  	if err != nil {
   235  		t.Fatalf("error: %v", err)
   236  	}
   237  	if stackFrame.Function.Name != "main.oneParameterAndOneVariable" {
   238  		t.Errorf("wrong function name: %s", stackFrame.Function.Name)
   239  	}
   240  	if stackFrame.Function.StartAddr != testutils.HelloworldAddrOneParameterAndVariable {
   241  		t.Errorf("wrong function value: %#x", stackFrame.Function.StartAddr)
   242  	}
   243  	if stackFrame.Function.EndAddr == 0 {
   244  		t.Errorf("end addr is 0")
   245  	}
   246  	if len(stackFrame.Function.Parameters) != 1 {
   247  		t.Errorf("wrong number of params")
   248  	}
   249  	if stackFrame.Function.Parameters[0].IsOutput {
   250  		t.Errorf("should be input parameter")
   251  	}
   252  }
   253  
   254  func TestFindFunction_FillInOneUnknownParameterOffset(t *testing.T) {
   255  	for i, testdata := range []uint64{
   256  		testutils.HelloworldAddrOneParameter,
   257  		testutils.HelloworldAddrErrorsNew,
   258  	} {
   259  		proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
   260  		if err != nil {
   261  			t.Fatalf("failed to launch process: %v", err)
   262  		}
   263  		defer proc.Detach()
   264  
   265  		if err := proc.SetBreakpoint(testutils.HelloworldAddrMain); err != nil {
   266  			t.Fatalf("failed to set breakpoint: %v", err)
   267  		}
   268  
   269  		if _, err := proc.ContinueAndWait(); err != nil {
   270  			t.Fatalf("failed to continue and wait: %v", err)
   271  		}
   272  
   273  		f, err := proc.FindFunction(testdata)
   274  		if err != nil {
   275  			t.Fatalf("[%d] failed to find func for %x: %v", i, testdata, err)
   276  		}
   277  
   278  		numNotExist := 0
   279  		numOffset0 := 0
   280  		for _, param := range f.Parameters {
   281  			if !param.Exist {
   282  				numNotExist++
   283  			}
   284  			if param.Offset == 0 {
   285  				numOffset0++
   286  			}
   287  		}
   288  		if numNotExist == 1 {
   289  			t.Errorf("The number of NonExist parameter is 1, params: %#v", f.Parameters)
   290  		}
   291  		if numOffset0 != 1 {
   292  			t.Errorf("The number of offset 0 parameter is %d, params: %#v", numOffset0, f.Parameters)
   293  		}
   294  	}
   295  }
   296  
   297  func TestFindFunction_FillInOutputParametersOffset(t *testing.T) {
   298  	proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
   299  	if err != nil {
   300  		t.Fatalf("failed to launch process: %v", err)
   301  	}
   302  	defer proc.Detach()
   303  
   304  	if err := proc.SetBreakpoint(testutils.HelloworldAddrMain); err != nil {
   305  		t.Fatalf("failed to set breakpoint: %v", err)
   306  	}
   307  
   308  	if _, err := proc.ContinueAndWait(); err != nil {
   309  		t.Fatalf("failed to continue and wait: %v", err)
   310  	}
   311  
   312  	f, err := proc.FindFunction(testutils.HelloworldAddrTwoReturns)
   313  	if err != nil {
   314  		t.Fatalf("failed to find func: %v", err)
   315  	}
   316  
   317  	if !f.Parameters[0].Exist || f.Parameters[0].Offset != 0 || f.Parameters[0].Name != "~r0" {
   318  		t.Errorf("Invalid parameter: %#v", f.Parameters[0])
   319  	}
   320  	if !f.Parameters[1].Exist || f.Parameters[1].Offset != 8 || f.Parameters[1].Name != "~r1" {
   321  		t.Errorf("Invalid parameter: %#v", f.Parameters[1])
   322  	}
   323  }
   324  
   325  func TestFuncTypeOffsets(t *testing.T) {
   326  	binary, _ := OpenBinaryFile(testutils.ProgramHelloworld, GoVersion{})
   327  	debuggableBinary, _ := binary.(debuggableBinaryFile)
   328  
   329  	entry, err := debuggableBinary.findDWARFEntryByName(func(entry *dwarf.Entry) bool {
   330  		if entry.Tag != dwarf.TagStructType {
   331  			return false
   332  		}
   333  		name, err := stringClassAttr(entry, dwarf.AttrName)
   334  		return name == "runtime._func" && err == nil
   335  	})
   336  	if err != nil {
   337  		t.Fatalf("no _func type entry: %v", err)
   338  	}
   339  
   340  	expectedFuncType, err := debuggableBinary.dwarf.Type(entry.Offset)
   341  	if err != nil {
   342  		t.Fatalf("no func type: %v", err)
   343  	}
   344  
   345  	expectedFields := expectedFuncType.(*dwarf.StructType).Field
   346  	for _, actualField := range _funcType.Field {
   347  		for _, expectedField := range expectedFields {
   348  			if actualField.Name == expectedField.Name {
   349  				if actualField.ByteOffset != expectedField.ByteOffset {
   350  					t.Errorf("wrong byte offset. expect: %d, actual: %d", expectedField.ByteOffset, actualField.ByteOffset)
   351  				}
   352  				if actualField.Type.Size() != expectedField.Type.Size() {
   353  					t.Errorf("wrong size. expect: %d, actual: %d", expectedField.Type.Size(), actualField.Type.Size())
   354  				}
   355  				break
   356  			}
   357  		}
   358  	}
   359  }
   360  
   361  func TestFindfuncbucketTypeOffsets(t *testing.T) {
   362  	if !ParseGoVersion(runtime.Version()).LaterThan(GoVersion{MajorVersion: 1, MinorVersion: 11}) {
   363  		t.Skip("go1.10 or earlier doesn't have findfuncbucket type in DWARF")
   364  	}
   365  
   366  	binary, _ := OpenBinaryFile(testutils.ProgramHelloworld, GoVersion{})
   367  	debuggableBinary, _ := binary.(debuggableBinaryFile)
   368  
   369  	entry, err := debuggableBinary.findDWARFEntryByName(func(entry *dwarf.Entry) bool {
   370  		if entry.Tag != dwarf.TagStructType {
   371  			return false
   372  		}
   373  		name, err := stringClassAttr(entry, dwarf.AttrName)
   374  		return name == "runtime.findfuncbucket" && err == nil
   375  	})
   376  	if err != nil {
   377  		t.Fatalf("no findfuncbucket type entry: %v", err)
   378  	}
   379  
   380  	expectedFindfuncbucketType, err := debuggableBinary.dwarf.Type(entry.Offset)
   381  	if err != nil {
   382  		t.Fatalf("no findfuncbucket type: %v", err)
   383  	}
   384  
   385  	expectedFields := expectedFindfuncbucketType.(*dwarf.StructType).Field
   386  	for _, actualField := range findfuncbucketType.Field {
   387  		for _, expectedField := range expectedFields {
   388  			if actualField.Name == expectedField.Name {
   389  				if actualField.ByteOffset != expectedField.ByteOffset {
   390  					t.Errorf("wrong byte offset. expect: %d, actual: %d", expectedField.ByteOffset, actualField.ByteOffset)
   391  				}
   392  				if actualField.Type.Size() != expectedField.Type.Size() {
   393  					t.Errorf("wrong size. expect: %d, actual: %d", expectedField.Type.Size(), actualField.Type.Size())
   394  				}
   395  				break
   396  			}
   397  		}
   398  	}
   399  }
   400  
   401  func TestReadInstructions(t *testing.T) {
   402  	for _, testdata := range []struct {
   403  		program  string
   404  		funcAddr uint64
   405  		targetOS string // specify if the func is not available in some OSes. Keep it empty otherwise.
   406  	}{
   407  		{testutils.ProgramHelloworld, testutils.HelloworldAddrMain, ""},
   408  		{testutils.ProgramHelloworldNoDwarf, testutils.HelloworldAddrMain, ""},     // includes the last 0xcc insts
   409  		{testutils.ProgramHelloworld, testutils.HelloworldAddrGoBuildID, "darwin"}, // includes bad insts
   410  	} {
   411  		if testdata.targetOS != "" && testdata.targetOS != runtime.GOOS {
   412  			continue
   413  		}
   414  
   415  		proc, err := LaunchProcess(testdata.program, nil, helloworldAttr)
   416  		if err != nil {
   417  			t.Fatalf("failed to launch process: %v", err)
   418  		}
   419  		defer proc.Detach()
   420  
   421  		f, err := proc.FindFunction(testdata.funcAddr)
   422  		if err != nil {
   423  			t.Fatalf("failed to find function: %v", err)
   424  		}
   425  		insts, err := proc.ReadInstructions(f)
   426  		if err != nil {
   427  			t.Fatalf("failed to read instructions: %v", err)
   428  		}
   429  
   430  		if len(insts) == 0 {
   431  			t.Errorf("empty insts")
   432  		}
   433  	}
   434  }
   435  
   436  func TestReadInstructions_SetBreakpointBefore(t *testing.T) {
   437  	proc, err := LaunchProcess(testutils.ProgramHelloworld, nil, helloworldAttr)
   438  	if err != nil {
   439  		t.Fatalf("failed to launch process: %v", err)
   440  	}
   441  	defer proc.Detach()
   442  
   443  	f, err := proc.FindFunction(testutils.HelloworldAddrMain)
   444  	if err != nil {
   445  		t.Fatalf("failed to find function: %v", err)
   446  	}
   447  
   448  	proc.SetBreakpoint(f.StartAddr)
   449  
   450  	insts, err := proc.ReadInstructions(f)
   451  	if err != nil {
   452  		t.Fatalf("failed to read instructions: %v", err)
   453  	}
   454  
   455  	if len(insts) == 0 {
   456  		t.Errorf("empty insts")
   457  	}
   458  	if insts[0].Op == x86asm.INT {
   459  		t.Errorf("breakpoint is not reset")
   460  	}
   461  }
   462  
   463  func TestCurrentGoRoutineInfo(t *testing.T) {
   464  	for i, testProgram := range []string{testutils.ProgramHelloworld, testutils.ProgramHelloworldNoDwarf} {
   465  		proc, err := LaunchProcess(testProgram, nil, helloworldAttr)
   466  		if err != nil {
   467  			t.Fatalf("[%d] failed to launch process: %v", i, err)
   468  		}
   469  		defer proc.Detach()
   470  
   471  		if err := proc.SetBreakpoint(testutils.HelloworldAddrMain); err != nil {
   472  			t.Fatalf("[%d] failed to set breakpoint: %v", i, err)
   473  		}
   474  
   475  		event, err := proc.ContinueAndWait()
   476  		if err != nil {
   477  			t.Fatalf("[%d] failed to continue and wait: %v", i, err)
   478  		}
   479  
   480  		threadIDs := event.Data.([]int)
   481  		goRoutineInfo, err := proc.CurrentGoRoutineInfo(threadIDs[0])
   482  		if err != nil {
   483  			t.Fatalf("[%d] error: %v", i, err)
   484  		}
   485  		if goRoutineInfo.ID != 1 {
   486  			t.Errorf("[%d] wrong id: %d", i, goRoutineInfo.ID)
   487  		}
   488  		if goRoutineInfo.UsedStackSize == 0 {
   489  			t.Errorf("[%d] wrong stack size: %d", i, goRoutineInfo.UsedStackSize)
   490  		}
   491  		if goRoutineInfo.CurrentPC != testutils.HelloworldAddrMain+1 {
   492  			t.Errorf("[%d] wrong pc: %d", i, goRoutineInfo.CurrentPC)
   493  		}
   494  		if goRoutineInfo.CurrentStackAddr == 0 {
   495  			t.Errorf("[%d] current stack address is 0", i)
   496  		}
   497  		if goRoutineInfo.NextDeferFuncAddr == 0 {
   498  			t.Errorf("[%d] NextDeferFuncAddr is 0", i)
   499  		}
   500  		if goRoutineInfo.Panicking {
   501  			t.Errorf("[%d] panicking", i)
   502  		}
   503  		// main go routine always has 'defer' setting. See runtime.main() for the detail.
   504  		if goRoutineInfo.PanicHandler == nil || goRoutineInfo.PanicHandler.PCAtDefer == 0 || goRoutineInfo.PanicHandler.UsedStackSizeAtDefer == 0 {
   505  			t.Errorf("[%d] deferedBy is nil or its value is 0", i)
   506  		}
   507  	}
   508  }
   509  
   510  func TestCurrentGoRoutineInfo_Panicking(t *testing.T) {
   511  	for _, testProgram := range []string{testutils.ProgramPanic, testutils.ProgramPanicNoDwarf} {
   512  		proc, err := LaunchProcess(testProgram, nil, helloworldAttr)
   513  		if err != nil {
   514  			t.Fatalf("failed to launch process: %v", err)
   515  		}
   516  		defer proc.Detach()
   517  
   518  		if err := proc.SetBreakpoint(testutils.PanicAddrInsideThrough); err != nil {
   519  			t.Fatalf("failed to set breakpoint: %v", err)
   520  		}
   521  
   522  		event, err := proc.ContinueAndWait()
   523  		if err != nil {
   524  			t.Fatalf("failed to continue and wait: %v", err)
   525  		}
   526  
   527  		tids := event.Data.([]int)
   528  		goRoutineInfo, err := proc.CurrentGoRoutineInfo(tids[0])
   529  		if err != nil {
   530  			t.Fatalf("error: %v", err)
   531  		}
   532  		if !goRoutineInfo.Panicking {
   533  			t.Errorf("not panicking")
   534  		}
   535  
   536  		if goRoutineInfo.PanicHandler.PCAtDefer == 0 {
   537  			t.Errorf("invalid panic handler")
   538  		}
   539  	}
   540  }
   541  
   542  func TestArgument_ParseValue(t *testing.T) {
   543  	for i, testdata := range []struct {
   544  		arg      Argument
   545  		expected string
   546  	}{
   547  		{Argument{Name: "a", parseValue: func(int) value { return int8Value{val: 1} }}, "a = 1"},
   548  		{Argument{Name: "a", parseValue: func(int) value { return nil }}, "a = -"},
   549  		{Argument{Name: "", parseValue: func(int) value { return int8Value{val: 1} }}, "1"},
   550  	} {
   551  		actual := testdata.arg.ParseValue(0)
   552  		if actual != testdata.expected {
   553  			t.Errorf("[%d] wrong parsed result. expect: %s, actual %s", i, testdata.expected, actual)
   554  		}
   555  	}
   556  
   557  }