ruby-static-tracing 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/ext/ruby-static-tracing/darwin/provider.c +128 -0
- data/ext/ruby-static-tracing/darwin/provider.h +62 -0
- data/ext/ruby-static-tracing/darwin/ruby_static_tracing.c +48 -0
- data/ext/ruby-static-tracing/darwin/tracepoint.c +231 -0
- data/ext/ruby-static-tracing/darwin/tracepoint.h +52 -0
- data/ext/ruby-static-tracing/extconf.rb +45 -5
- data/ext/ruby-static-tracing/{linux → include}/ruby_static_tracing.h +3 -6
- data/ext/ruby-static-tracing/lib/deps-extconf.rb +47 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/example/demo.c +42 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/dynamic-symbols.c +41 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/dynamic-symbols.h +34 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/errors.c +30 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/errors.h +8 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/hash-table.c +27 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/hash-table.h +3 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/libstapsdt.c +208 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/libstapsdt.h +66 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/sdtnote.c +176 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/sdtnote.h +46 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/section.c +30 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/section.h +21 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/shared-lib.c +563 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/shared-lib.h +46 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/string-table.c +67 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/string-table.h +28 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/util.c +12 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/src/util.h +6 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/tests/test-errors.c +77 -0
- data/ext/ruby-static-tracing/lib/libstapsdt/tests/test-memory-leaks.c +25 -0
- data/ext/ruby-static-tracing/lib/post-extconf.rb +37 -0
- data/ext/ruby-static-tracing/linux/provider.c +1 -1
- data/ext/ruby-static-tracing/linux/provider.h +2 -7
- data/ext/ruby-static-tracing/linux/ruby_static_tracing.c +4 -3
- data/ext/ruby-static-tracing/linux/tracepoint.c +0 -1
- data/ext/ruby-static-tracing/linux/tracepoint.h +1 -5
- data/ext/ruby-static-tracing/linux/types.h +11 -0
- data/lib/ruby-static-tracing.rb +5 -7
- data/lib/ruby-static-tracing/platform.rb +48 -0
- data/lib/ruby-static-tracing/provider.rb +18 -1
- data/lib/ruby-static-tracing/tracepoint.rb +15 -3
- data/lib/ruby-static-tracing/tracepoints.rb +36 -0
- data/lib/ruby-static-tracing/tracers.rb +1 -0
- data/lib/ruby-static-tracing/tracers/base.rb +68 -0
- data/lib/ruby-static-tracing/tracers/helpers.rb +17 -0
- data/lib/ruby-static-tracing/tracers/latency_tracer.rb +13 -60
- data/lib/ruby-static-tracing/tracers/stack_tracer.rb +19 -0
- data/lib/ruby-static-tracing/version.rb +1 -1
- metadata +41 -5
@@ -0,0 +1,46 @@
|
|
1
|
+
#ifndef _SHARED_LIB_H
|
2
|
+
#define _SHARED_LIB_H
|
3
|
+
|
4
|
+
#include "section.h"
|
5
|
+
#include "string-table.h"
|
6
|
+
#include "sdtnote.h"
|
7
|
+
#include "dynamic-symbols.h"
|
8
|
+
|
9
|
+
typedef struct {
|
10
|
+
Section
|
11
|
+
*hash,
|
12
|
+
*dynSym,
|
13
|
+
*dynStr,
|
14
|
+
*text,
|
15
|
+
*sdtBase,
|
16
|
+
*ehFrame,
|
17
|
+
*dynamic,
|
18
|
+
*sdtNote,
|
19
|
+
*shStrTab;
|
20
|
+
} SectionsList;
|
21
|
+
|
22
|
+
typedef struct {
|
23
|
+
Elf *elf;
|
24
|
+
Elf64_Ehdr *ehdr;
|
25
|
+
Elf64_Phdr *phdrLoad1, *phdrLoad2, *phdrDyn, *phdrStack;
|
26
|
+
|
27
|
+
StringTable *stringTable;
|
28
|
+
StringTable *dynamicString;
|
29
|
+
|
30
|
+
DynamicSymbolTable *dynamicSymbols;
|
31
|
+
|
32
|
+
SDTNoteList_t *sdtNotes;
|
33
|
+
size_t sdtNotesCount;
|
34
|
+
|
35
|
+
SectionsList sections;
|
36
|
+
} DynElf;
|
37
|
+
|
38
|
+
DynElf *dynElfInit();
|
39
|
+
|
40
|
+
int dynElfAddProbe(DynElf *dynElf, SDTProbe_t *probe);
|
41
|
+
|
42
|
+
int dynElfSave(DynElf *dynElf);
|
43
|
+
|
44
|
+
void dynElfClose(DynElf *dynElf);
|
45
|
+
|
46
|
+
#endif
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
|
4
|
+
#include "string-table.h"
|
5
|
+
|
6
|
+
StringTable *stringTableInit() {
|
7
|
+
StringTable *stringTable = (StringTable *)calloc(sizeof(StringTable), 1);
|
8
|
+
stringTable->count = 1;
|
9
|
+
stringTable->size = 1;
|
10
|
+
|
11
|
+
stringTable->first = (StringTableNode *)calloc(sizeof(StringTableNode), 1);
|
12
|
+
|
13
|
+
stringTable->first->index = 0;
|
14
|
+
stringTable->first->size = 1;
|
15
|
+
stringTable->first->str = (char *)calloc(sizeof(char), 1);
|
16
|
+
stringTable->first->str[0] = '\0';
|
17
|
+
stringTable->first->next = NULL;
|
18
|
+
|
19
|
+
return stringTable;
|
20
|
+
}
|
21
|
+
|
22
|
+
StringTableNode *stringTableAdd(StringTable *stringTable, char *str) {
|
23
|
+
StringTableNode *current;
|
24
|
+
|
25
|
+
for (current = stringTable->first; current->next != NULL;
|
26
|
+
current = current->next) {
|
27
|
+
}
|
28
|
+
|
29
|
+
current->next = (StringTableNode *)calloc(sizeof(StringTableNode), 1);
|
30
|
+
current->next->index = current->index + current->size;
|
31
|
+
|
32
|
+
current = current->next;
|
33
|
+
current->size = strlen(str) + 1;
|
34
|
+
|
35
|
+
current->str = (char *)calloc(current->size, 1);
|
36
|
+
memcpy(current->str, str, current->size);
|
37
|
+
current->next = NULL;
|
38
|
+
|
39
|
+
stringTable->count += 1;
|
40
|
+
stringTable->size += current->size;
|
41
|
+
|
42
|
+
return current;
|
43
|
+
}
|
44
|
+
|
45
|
+
char *stringTableToBuffer(StringTable *stringTable) {
|
46
|
+
int offset;
|
47
|
+
StringTableNode *current;
|
48
|
+
char *buffer = (char *)calloc(stringTable->size, 1);
|
49
|
+
|
50
|
+
for (current = stringTable->first, offset = 0; current != NULL;
|
51
|
+
offset += current->size, current = current->next) {
|
52
|
+
memcpy(&buffer[offset], current->str, current->size);
|
53
|
+
}
|
54
|
+
|
55
|
+
return buffer;
|
56
|
+
}
|
57
|
+
|
58
|
+
void stringTableFree(StringTable *table) {
|
59
|
+
StringTableNode *node=NULL, *next=NULL;
|
60
|
+
for(node=table->first; node!=NULL; node=next) {
|
61
|
+
free(node->str);
|
62
|
+
|
63
|
+
next=node->next;
|
64
|
+
free(node);
|
65
|
+
}
|
66
|
+
free(table);
|
67
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#ifndef _STRING_TABLE_H
|
2
|
+
#define _STRING_TABLE_H
|
3
|
+
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <string.h>
|
6
|
+
|
7
|
+
typedef struct StringTableNode_ {
|
8
|
+
int index;
|
9
|
+
int size;
|
10
|
+
char *str;
|
11
|
+
struct StringTableNode_ *next;
|
12
|
+
} StringTableNode;
|
13
|
+
|
14
|
+
typedef struct {
|
15
|
+
int count;
|
16
|
+
size_t size;
|
17
|
+
StringTableNode *first;
|
18
|
+
} StringTable;
|
19
|
+
|
20
|
+
StringTable *stringTableInit();
|
21
|
+
|
22
|
+
StringTableNode *stringTableAdd(StringTable *stringTable, char *str);
|
23
|
+
|
24
|
+
char *stringTableToBuffer(StringTable *stringTable);
|
25
|
+
|
26
|
+
void stringTableFree(StringTable *stringTable);
|
27
|
+
|
28
|
+
#endif
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#include "libstapsdt.h"
|
2
|
+
#include <stdio.h>
|
3
|
+
#include <unistd.h>
|
4
|
+
#include <dlfcn.h>
|
5
|
+
|
6
|
+
int testElfCreationError() {
|
7
|
+
// TODO (mmarchini) write test case for elf creation error
|
8
|
+
return 1;
|
9
|
+
}
|
10
|
+
|
11
|
+
int testTmpCreationError() {
|
12
|
+
SDTProvider_t *provider;
|
13
|
+
SDTError_t errno;
|
14
|
+
provider = providerInit("test/probe/creation/error");
|
15
|
+
if(providerLoad(provider) == 0) {
|
16
|
+
return 0;
|
17
|
+
}
|
18
|
+
printf("[testTmpCreationError] Error message: %s\n", provider->error);
|
19
|
+
errno = provider->errno;
|
20
|
+
providerDestroy(provider);
|
21
|
+
return errno == tmpCreationError;
|
22
|
+
}
|
23
|
+
|
24
|
+
int testSharedLibraryOpenError() {
|
25
|
+
// TODO (mmarchini) write test case for shared library loading error
|
26
|
+
return 1;
|
27
|
+
}
|
28
|
+
|
29
|
+
int testSymbolLoadingError() {
|
30
|
+
// TODO (mmarchini) write test case for symbol loading error
|
31
|
+
return 1;
|
32
|
+
}
|
33
|
+
|
34
|
+
int testSharedLibraryCloseError() {
|
35
|
+
SDTProvider_t *provider;
|
36
|
+
SDTError_t errno;
|
37
|
+
provider = providerInit("test-error");
|
38
|
+
providerLoad(provider);
|
39
|
+
dlclose(provider->_handle);
|
40
|
+
if(providerUnload(provider) == 0) {
|
41
|
+
return 0;
|
42
|
+
}
|
43
|
+
errno = provider->errno;
|
44
|
+
printf("[testSharedLibraryCloseError] Error message: %s\n", provider->error);
|
45
|
+
providerDestroy(provider);
|
46
|
+
return errno == sharedLibraryCloseError;
|
47
|
+
}
|
48
|
+
|
49
|
+
int main() {
|
50
|
+
if (!testElfCreationError()) {
|
51
|
+
printf("Test case failed: testElfCreationError\n");
|
52
|
+
return -1;
|
53
|
+
}
|
54
|
+
|
55
|
+
if (!testTmpCreationError()) {
|
56
|
+
printf("Test case failed: testTmpCreationError\n");
|
57
|
+
return -2;
|
58
|
+
}
|
59
|
+
|
60
|
+
if (!testSharedLibraryOpenError()) {
|
61
|
+
printf("Test case failed: testSharedLibraryOpenError\n");
|
62
|
+
return -3;
|
63
|
+
}
|
64
|
+
|
65
|
+
if (!testSymbolLoadingError()) {
|
66
|
+
printf("Test case failed: testSymbolLoadingError\n");
|
67
|
+
return -4;
|
68
|
+
}
|
69
|
+
|
70
|
+
if (!testSharedLibraryCloseError()) {
|
71
|
+
printf("Test case failed: testSharedLibraryCloseError\n");
|
72
|
+
return -5;
|
73
|
+
}
|
74
|
+
|
75
|
+
|
76
|
+
return 0;
|
77
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <unistd.h>
|
3
|
+
#include <libstapsdt.h>
|
4
|
+
|
5
|
+
int main( int argc, char *argv[] ) {
|
6
|
+
SDTProvider_t *provider;
|
7
|
+
SDTProbe_t *probe1, *probe2;
|
8
|
+
|
9
|
+
provider = providerInit("testProvider");
|
10
|
+
probe1 = providerAddProbe(provider, "testProbe1", 4, int8, uint8, int64, uint64);
|
11
|
+
probe2 = providerAddProbe(provider, "testProbe2", 2, int8, uint8);
|
12
|
+
|
13
|
+
if(providerLoad(provider) == -1) {
|
14
|
+
printf("Something went wrong...\n");
|
15
|
+
return -1;
|
16
|
+
}
|
17
|
+
|
18
|
+
probeFire(probe1, 1, 2, 3, 4);
|
19
|
+
probeFire(probe2, -3, 8);
|
20
|
+
|
21
|
+
providerUnload(provider);
|
22
|
+
providerDestroy(provider);
|
23
|
+
|
24
|
+
return 0;
|
25
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__)
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
require 'ruby-static-tracing/platform'
|
5
|
+
|
6
|
+
BASE_DIR = File.expand_path(File.dirname(__FILE__))
|
7
|
+
LIB_DIR = File.expand_path('../../../../lib/ruby-static-tracing', __FILE__)
|
8
|
+
|
9
|
+
# Linux is a noop
|
10
|
+
if StaticTracing::Platform.linux?
|
11
|
+
File.write "Makefile", <<MAKEFILE
|
12
|
+
all:
|
13
|
+
touch post.so
|
14
|
+
clean:
|
15
|
+
install:
|
16
|
+
MAKEFILE
|
17
|
+
exit
|
18
|
+
# We'll build libusdt and install and update linker info
|
19
|
+
elsif StaticTracing::Platform.darwin?
|
20
|
+
# This is done to ensure that the bundle will look in its local directory for the library
|
21
|
+
File.write "Makefile", <<MAKEFILE
|
22
|
+
all:
|
23
|
+
touch post.bundle
|
24
|
+
install_name_tool -change libusdt.dylib @loader_path/../ruby-static-tracing/libusdt.dylib #{File.join(LIB_DIR, 'ruby_static_tracing.bundle')}
|
25
|
+
clean:
|
26
|
+
install:
|
27
|
+
MAKEFILE
|
28
|
+
exit
|
29
|
+
else
|
30
|
+
# - Stub, for other platforms that we don't support, we write an empty makefile
|
31
|
+
File.write "Makefile", <<MAKEFILE
|
32
|
+
all:
|
33
|
+
clean:
|
34
|
+
install:
|
35
|
+
MAKEFILE
|
36
|
+
exit
|
37
|
+
end
|
@@ -19,7 +19,7 @@ provider_initialize(VALUE self, VALUE name)
|
|
19
19
|
// Check and cast arguments
|
20
20
|
c_name_str = check_name_arg(name);
|
21
21
|
|
22
|
-
// Build
|
22
|
+
// Build provider structure
|
23
23
|
TypedData_Get_Struct(self, static_tracing_provider_t, &static_tracing_provider_type, res);
|
24
24
|
res->sdt_provider = providerInit(c_name_str);
|
25
25
|
return self;
|
@@ -1,16 +1,11 @@
|
|
1
|
-
/*
|
2
|
-
For core static tracing functions exposed directly to ruby.
|
3
|
-
Functions here are associated with rubyland operations.
|
4
|
-
*/
|
5
1
|
#ifndef STATIC_TRACING_PROVIDER_H
|
6
2
|
#define STATIC_TRACING_PROVIDER_H
|
7
3
|
|
8
|
-
#include <ruby.h>
|
9
4
|
// Include libstapsdt.h to wrap
|
10
|
-
#include <libstapsdt.h>
|
5
|
+
#include <libstapsdt.h> // FIXME use local
|
11
6
|
|
7
|
+
#include "types.h"
|
12
8
|
#include "ruby_static_tracing.h"
|
13
|
-
#include "tracepoint.h"
|
14
9
|
|
15
10
|
typedef struct {
|
16
11
|
char *name;
|
@@ -1,5 +1,6 @@
|
|
1
|
-
#include "
|
2
|
-
|
1
|
+
#include "ruby_static_tracing.h"
|
2
|
+
|
3
|
+
VALUE eUSDT, eInternal;
|
3
4
|
|
4
5
|
void Init_ruby_static_tracing()
|
5
6
|
{
|
@@ -41,7 +42,7 @@ void Init_ruby_static_tracing()
|
|
41
42
|
|
42
43
|
rb_define_alloc_func(cTracepoint, static_tracing_tracepoint_alloc);
|
43
44
|
rb_define_method(cTracepoint, "tracepoint_initialize", tracepoint_initialize, 3);
|
44
|
-
rb_define_method(cTracepoint, "
|
45
|
+
rb_define_method(cTracepoint, "_fire_tracepoint", tracepoint_fire, 1);
|
45
46
|
rb_define_method(cTracepoint, "enabled?", tracepoint_enabled, 0);
|
46
47
|
}
|
47
48
|
|
@@ -1,16 +1,12 @@
|
|
1
|
-
/*
|
2
|
-
For core static tracing functions exposed directly to ruby.
|
3
|
-
Functions here are associated with rubyland operations.
|
4
|
-
*/
|
5
1
|
#ifndef STATIC_TRACING_TRACEPOINT_H
|
6
2
|
#define STATIC_TRACING_TRACEPOINT_H
|
7
3
|
|
8
4
|
#include <ruby.h>
|
9
5
|
// Include libstapsdt.h to wrap
|
10
6
|
#include <libstapsdt.h>
|
11
|
-
// Probably need to include provider.h to be able to initialize self
|
12
7
|
|
13
8
|
#include "ruby_static_tracing.h"
|
9
|
+
#include "types.h"
|
14
10
|
|
15
11
|
typedef union {
|
16
12
|
unsigned long long intval;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#ifndef STATIC_TRACING_TYPES_H
|
2
|
+
#define STATIC_TRACING_TYPES_H
|
3
|
+
|
4
|
+
#include <libstapsdt.h>
|
5
|
+
|
6
|
+
typedef enum TRACEPOINT_ARG_TYPES_ENUM {
|
7
|
+
Integer = int64, // STAP enum type -8
|
8
|
+
String = uint64, // STAP enum type 8
|
9
|
+
} Tracepoint_arg_types;
|
10
|
+
|
11
|
+
#endif // STATIC_TRACING_TYPEs_H
|
data/lib/ruby-static-tracing.rb
CHANGED
@@ -6,6 +6,7 @@ require 'ruby-static-tracing/version'
|
|
6
6
|
require 'ruby-static-tracing/platform'
|
7
7
|
require 'ruby-static-tracing/provider'
|
8
8
|
require 'ruby-static-tracing/tracepoint'
|
9
|
+
require 'ruby-static-tracing/tracepoints'
|
9
10
|
require 'ruby-static-tracing/configuration'
|
10
11
|
require 'ruby-static-tracing/tracers'
|
11
12
|
|
@@ -45,12 +46,15 @@ module StaticTracing
|
|
45
46
|
# with a wrapped version that has tracing enabled
|
46
47
|
def enable!
|
47
48
|
tracers.each(&:enable!)
|
49
|
+
StaticTracing::Provider.enable!
|
48
50
|
@enabled = true
|
49
51
|
end
|
50
52
|
|
51
53
|
# Overwrite the definition of all functions to their original definition,
|
52
54
|
# no longer wrapping them
|
53
55
|
def disable!
|
56
|
+
tracers.each(&:disable!)
|
57
|
+
StaticTracing::Provider.disable!
|
54
58
|
@enabled = false
|
55
59
|
end
|
56
60
|
|
@@ -72,10 +76,4 @@ module StaticTracing
|
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
|
-
|
76
|
-
# within a trap handler.
|
77
|
-
# Specify default signals, but allow these to be overidden for easier integration
|
78
|
-
|
79
|
-
# This loads the actual C extension, we might want to guard it
|
80
|
-
# for cases where the extension isn't yet built
|
81
|
-
require 'ruby-static-tracing/ruby_static_tracing' if StaticTracing::Platform.linux?
|
79
|
+
require 'ruby-static-tracing/ruby_static_tracing' if StaticTracing::Platform.linux? || StaticTracing::Platform.darwin?
|
@@ -3,9 +3,57 @@
|
|
3
3
|
module StaticTracing
|
4
4
|
module Platform
|
5
5
|
extend self
|
6
|
+
UNSUPPORTED_POST_INSTALL_MESSAGE = %(
|
7
|
+
WARNING: You have installed this on an unsupported platform, somehow.
|
8
|
+
|
9
|
+
You should verify for yourself that the behavior on your platform is safe.
|
10
|
+
)
|
11
|
+
|
12
|
+
LINUX_POST_INSTALL_MESSAGE = %(
|
13
|
+
WARNING: you will need a new kernel (4.14+) that supports eBPF.
|
14
|
+
|
15
|
+
You should use the newest possible version of bpftrace
|
16
|
+
).freeze
|
17
|
+
|
18
|
+
DARWIN_POST_INSTALL_MESSAGE = %(
|
19
|
+
WARNING: tracing with dtrace will not work with SIP enabled.
|
20
|
+
|
21
|
+
SIP is enabled by default on recent versions of OSX. You can
|
22
|
+
check if SIP is enabled with:
|
23
|
+
|
24
|
+
csrutil status
|
25
|
+
|
26
|
+
If you want to test your probes out locally, you will need to at
|
27
|
+
least allow dtrace. To do this, you must reboot into recovery mode
|
28
|
+
by holding CMD + R while your Mac is booting. Once it has booted,
|
29
|
+
open a terminal and type:
|
30
|
+
|
31
|
+
csrutil clear
|
32
|
+
csrutil enable --without-dtrace
|
33
|
+
|
34
|
+
After this, you should be able to use dtrace on the tracepoints you
|
35
|
+
define here. If it still doesn't work, you can disable SIP entirely
|
36
|
+
but this is not recommended for security purposes.
|
37
|
+
).freeze
|
6
38
|
|
7
39
|
def linux?
|
8
40
|
/linux/.match(RUBY_PLATFORM)
|
9
41
|
end
|
42
|
+
|
43
|
+
def darwin?
|
44
|
+
/darwin/.match(RUBY_PLATFORM)
|
45
|
+
end
|
46
|
+
|
47
|
+
def post_install_message
|
48
|
+
message = begin
|
49
|
+
if linux?
|
50
|
+
LINUX_POST_INSTALL_MESSAGE
|
51
|
+
elsif darwin?
|
52
|
+
DARWIN_POST_INSTALL_MESSAGE
|
53
|
+
else
|
54
|
+
UNSUPPORTED_POST_INSTALL_MESSAGE
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
10
58
|
end
|
11
59
|
end
|