diff options
Diffstat (limited to 'src/pkg/runtime/symtab.c')
-rw-r--r-- | src/pkg/runtime/symtab.c | 740 |
1 files changed, 199 insertions, 541 deletions
diff --git a/src/pkg/runtime/symtab.c b/src/pkg/runtime/symtab.c index 578406247..dd0015aee 100644 --- a/src/pkg/runtime/symtab.c +++ b/src/pkg/runtime/symtab.c @@ -3,619 +3,268 @@ // license that can be found in the LICENSE file. // Runtime symbol table parsing. -// -// The Go tools use a symbol table derived from the Plan 9 symbol table -// format. The symbol table is kept in its own section treated as -// read-only memory when the binary is running: the binary consults the -// table. -// -// The format used by Go 1.0 was basically the Plan 9 format. Each entry -// is variable sized but had this format: -// -// 4-byte value, big endian -// 1-byte type ([A-Za-z] + 0x80) -// name, NUL terminated (or for 'z' and 'Z' entries, double-NUL terminated) -// 4-byte Go type address, big endian (new in Go) -// -// In order to support greater interoperation with standard toolchains, -// Go 1.1 uses a more flexible yet smaller encoding of the entries. -// The overall structure is unchanged from Go 1.0 and, for that matter, -// from Plan 9. -// -// The Go 1.1 table is a re-encoding of the data in a Go 1.0 table. -// To identify a new table as new, it begins one of two eight-byte -// sequences: -// -// FF FF FF FD 00 00 00 xx - big endian new table -// FD FF FF FF 00 00 00 xx - little endian new table -// -// This sequence was chosen because old tables stop at an entry with type -// 0, so old code reading a new table will see only an empty table. The -// first four bytes are the target-endian encoding of 0xfffffffd. The -// final xx gives AddrSize, the width of a full-width address. -// -// After that header, each entry is encoded as follows. -// -// 1-byte type (0-51 + two flag bits) -// AddrSize-byte value, host-endian OR varint-encoded value -// AddrSize-byte Go type address OR nothing -// [n] name, terminated as before -// -// The type byte comes first, but 'A' encodes as 0 and 'a' as 26, so that -// the type itself is only in the low 6 bits. The upper two bits specify -// the format of the next two fields. If the 0x40 bit is set, the value -// is encoded as an full-width 4- or 8-byte target-endian word. Otherwise -// the value is a varint-encoded number. If the 0x80 bit is set, the Go -// type is present, again as a 4- or 8-byte target-endian word. If not, -// there is no Go type in this entry. The NUL-terminated name ends the -// entry. +// See http://golang.org/s/go12symtab for an overview. #include "runtime.h" #include "defs_GOOS_GOARCH.h" #include "os_GOOS.h" #include "arch_GOARCH.h" #include "malloc.h" +#include "funcdata.h" -extern byte pclntab[], epclntab[], symtab[], esymtab[]; - -typedef struct Sym Sym; -struct Sym +typedef struct Ftab Ftab; +struct Ftab { - uintptr value; - byte symtype; - byte *name; -// byte *gotype; + uintptr entry; + uintptr funcoff; }; -static uintptr mainoffset; +extern byte pclntab[]; -// A dynamically allocated string containing multiple substrings. -// Individual strings are slices of hugestring. -static String hugestring; -static int32 hugestring_len; +static Ftab *ftab; +static uintptr nftab; +static uint32 *filetab; +static uint32 nfiletab; -extern void main·main(void); +static String end = { (uint8*)"end", 3 }; -static uintptr -readword(byte **pp, byte *ep) +void +runtime·symtabinit(void) { - byte *p; - - p = *pp; - if(ep - p < sizeof(void*)) { - *pp = ep; - return 0; + int32 i, j; + Func *f1, *f2; + + // See golang.org/s/go12symtab for header: 0xfffffffb, + // two zero bytes, a byte giving the PC quantum, + // and a byte giving the pointer width in bytes. + if(*(uint32*)pclntab != 0xfffffffb || pclntab[4] != 0 || pclntab[5] != 0 || pclntab[6] != PCQuantum || pclntab[7] != sizeof(void*)) { + runtime·printf("runtime: function symbol table header: 0x%x 0x%x\n", *(uint32*)pclntab, *(uint32*)(pclntab+4)); + runtime·throw("invalid function symbol table\n"); } - *pp = p + sizeof(void*); - // Hairy, but only one of these four cases gets compiled. - if(sizeof(void*) == 8) { - if(BigEndian) { - return ((uint64)p[0]<<56) | ((uint64)p[1]<<48) | ((uint64)p[2]<<40) | ((uint64)p[3]<<32) | - ((uint64)p[4]<<24) | ((uint64)p[5]<<16) | ((uint64)p[6]<<8) | ((uint64)p[7]); + nftab = *(uintptr*)(pclntab+8); + ftab = (Ftab*)(pclntab+8+sizeof(void*)); + for(i=0; i<nftab; i++) { + // NOTE: ftab[nftab].entry is legal; it is the address beyond the final function. + if(ftab[i].entry > ftab[i+1].entry) { + f1 = (Func*)(pclntab + ftab[i].funcoff); + f2 = (Func*)(pclntab + ftab[i+1].funcoff); + runtime·printf("function symbol table not sorted by program counter: %p %s > %p %s", ftab[i].entry, runtime·funcname(f1), ftab[i+1].entry, i+1 == nftab ? "end" : runtime·funcname(f2)); + for(j=0; j<=i; j++) + runtime·printf("\t%p %s\n", ftab[j].entry, runtime·funcname((Func*)(pclntab + ftab[j].funcoff))); + runtime·throw("invalid runtime symbol table"); } - return ((uint64)p[7]<<56) | ((uint64)p[6]<<48) | ((uint64)p[5]<<40) | ((uint64)p[4]<<32) | - ((uint64)p[3]<<24) | ((uint64)p[2]<<16) | ((uint64)p[1]<<8) | ((uint64)p[0]); - } - if(BigEndian) { - return ((uint32)p[0]<<24) | ((uint32)p[1]<<16) | ((uint32)p[2]<<8) | ((uint32)p[3]); } - return ((uint32)p[3]<<24) | ((uint32)p[2]<<16) | ((uint32)p[1]<<8) | ((uint32)p[0]); + + filetab = (uint32*)(pclntab + *(uint32*)&ftab[nftab].funcoff); + nfiletab = filetab[0]; } -// Walk over symtab, calling fn(&s) for each symbol. -static void -walksymtab(void (*fn)(Sym*)) +static uint32 +readvarint(byte **pp) { - byte *p, *ep, *q; - Sym s; - int32 widevalue, havetype, shift; - - p = symtab; - ep = esymtab; - - // Table must begin with correct magic number. - if(ep - p < 8 || p[4] != 0x00 || p[5] != 0x00 || p[6] != 0x00 || p[7] != sizeof(void*)) - return; - if(BigEndian) { - if(p[0] != 0xff || p[1] != 0xff || p[2] != 0xff || p[3] != 0xfd) - return; - } else { - if(p[0] != 0xfd || p[1] != 0xff || p[2] != 0xff || p[3] != 0xff) - return; - } - p += 8; - - while(p < ep) { - s.symtype = p[0]&0x3F; - widevalue = p[0]&0x40; - havetype = p[0]&0x80; - if(s.symtype < 26) - s.symtype += 'A'; - else - s.symtype += 'a' - 26; - p++; - - // Value, either full-width or varint-encoded. - if(widevalue) { - s.value = readword(&p, ep); - } else { - s.value = 0; - shift = 0; - while(p < ep && (p[0]&0x80) != 0) { - s.value |= (uintptr)(p[0]&0x7F)<<shift; - shift += 7; - p++; - } - if(p >= ep) - break; - s.value |= (uintptr)p[0]<<shift; - p++; - } - - // Go type, if present. Ignored but must skip over. - if(havetype) - readword(&p, ep); - - // Name. - if(ep - p < 2) - break; - - s.name = p; - if(s.symtype == 'z' || s.symtype == 'Z') { - // path reference string - skip first byte, - // then 2-byte pairs ending at two zeros. - q = p+1; - for(;;) { - if(q+2 > ep) - return; - if(q[0] == '\0' && q[1] == '\0') - break; - q += 2; - } - p = q+2; - }else{ - q = runtime·mchr(p, '\0', ep); - if(q == nil) - break; - p = q+1; - } + byte *p; + uint32 v; + int32 shift; - fn(&s); + v = 0; + p = *pp; + for(shift = 0;; shift += 7) { + v |= (*p & 0x7F) << shift; + if(!(*p++ & 0x80)) + break; } + *pp = p; + return v; } -// Symtab walker; accumulates info about functions. - -static Func *func; -static int32 nfunc; - -static byte **fname; -static int32 nfname; - -static uint32 funcinit; -static Lock funclock; -static uintptr lastvalue; - -static void -dofunc(Sym *sym) +void* +runtime·funcdata(Func *f, int32 i) { - Func *f; - - switch(sym->symtype) { - case 't': - case 'T': - case 'l': - case 'L': - if(runtime·strcmp(sym->name, (byte*)"etext") == 0) - break; - if(sym->value < lastvalue) { - runtime·printf("symbols out of order: %p before %p\n", lastvalue, sym->value); - runtime·throw("malformed symbol table"); - } - lastvalue = sym->value; - if(func == nil) { - nfunc++; - break; - } - f = &func[nfunc++]; - f->name = runtime·gostringnocopy(sym->name); - f->entry = sym->value; - if(sym->symtype == 'L' || sym->symtype == 'l') - f->frame = -sizeof(uintptr); - break; - case 'm': - if(nfunc <= 0 || func == nil) - break; - if(runtime·strcmp(sym->name, (byte*)".frame") == 0) - func[nfunc-1].frame = sym->value; - else if(runtime·strcmp(sym->name, (byte*)".locals") == 0) - func[nfunc-1].locals = sym->value; - else if(runtime·strcmp(sym->name, (byte*)".args") == 0) - func[nfunc-1].args = sym->value; - else { - runtime·printf("invalid 'm' symbol named '%s'\n", sym->name); - runtime·throw("mangled symbol table"); - } - break; - case 'f': - if(fname == nil) { - if(sym->value >= nfname) { - if(sym->value >= 0x10000) { - runtime·printf("runtime: invalid symbol file index %p\n", sym->value); - runtime·throw("mangled symbol table"); - } - nfname = sym->value+1; - } - break; - } - fname[sym->value] = sym->name; - break; - } + byte *p; + + if(i < 0 || i >= f->nfuncdata) + return nil; + p = (byte*)&f->nfuncdata + 4 + f->npcdata*4; + if(sizeof(void*) == 8 && ((uintptr)p & 4)) + p += 4; + return ((void**)p)[i]; } -// put together the path name for a z entry. -// the f entries have been accumulated into fname already. -// returns the length of the path name. -static int32 -makepath(byte *buf, int32 nbuf, byte *path) +static bool +step(byte **pp, uintptr *pc, int32 *value, bool first) { - int32 n, len; - byte *p, *ep, *q; + uint32 uvdelta, pcdelta; + int32 vdelta; - if(nbuf <= 0) + uvdelta = readvarint(pp); + if(uvdelta == 0 && !first) return 0; - - p = buf; - ep = buf + nbuf; - *p = '\0'; - for(;;) { - if(path[0] == 0 && path[1] == 0) - break; - n = (path[0]<<8) | path[1]; - path += 2; - if(n >= nfname) - break; - q = fname[n]; - len = runtime·findnull(q); - if(p+1+len >= ep) - break; - if(p > buf && p[-1] != '/') - *p++ = '/'; - runtime·memmove(p, q, len+1); - p += len; - } - return p - buf; + if(uvdelta&1) + uvdelta = ~(uvdelta>>1); + else + uvdelta >>= 1; + vdelta = (int32)uvdelta; + pcdelta = readvarint(pp) * PCQuantum; + *value += vdelta; + *pc += pcdelta; + return 1; } -// appends p to hugestring -static String -gostringn(byte *p, int32 l) +// Return associated data value for targetpc in func f. +// (Source file is f->src.) +static int32 +pcvalue(Func *f, int32 off, uintptr targetpc, bool strict) { - String s; + byte *p; + uintptr pc; + int32 value; + + enum { + debug = 0 + }; - if(l == 0) - return runtime·emptystring; - if(hugestring.str == nil) { - hugestring_len += l; - return runtime·emptystring; + // The table is a delta-encoded sequence of (value, pc) pairs. + // Each pair states the given value is in effect up to pc. + // The value deltas are signed, zig-zag encoded. + // The pc deltas are unsigned. + // The starting value is -1, the starting pc is the function entry. + // The table ends at a value delta of 0 except in the first pair. + if(off == 0) + return -1; + p = pclntab + off; + pc = f->entry; + value = -1; + + if(debug && !runtime·panicking) + runtime·printf("pcvalue start f=%s [%p] pc=%p targetpc=%p value=%d tab=%p\n", + runtime·funcname(f), f, pc, targetpc, value, p); + + while(step(&p, &pc, &value, pc == f->entry)) { + if(debug) + runtime·printf("\tvalue=%d until pc=%p\n", value, pc); + if(targetpc < pc) + return value; } - s.str = hugestring.str + hugestring.len; - s.len = l; - hugestring.len += s.len; - runtime·memmove(s.str, p, l); - return s; + + // If there was a table, it should have covered all program counters. + // If not, something is wrong. + if(runtime·panicking || !strict) + return -1; + runtime·printf("runtime: invalid pc-encoded table f=%s pc=%p targetpc=%p tab=%p\n", + runtime·funcname(f), pc, targetpc, p); + p = (byte*)f + off; + pc = f->entry; + value = -1; + + while(step(&p, &pc, &value, pc == f->entry)) + runtime·printf("\tvalue=%d until pc=%p\n", value, pc); + + runtime·throw("invalid runtime symbol table"); + return -1; } -// walk symtab accumulating path names for use by pc/ln table. -// don't need the full generality of the z entry history stack because -// there are no includes in go (and only sensible includes in our c); -// assume code only appear in top-level files. -static void -dosrcline(Sym *sym) +static String unknown = { (uint8*)"?", 1 }; + +int8* +runtime·funcname(Func *f) { - static byte srcbuf[1000]; - static struct { - String srcstring; - int32 aline; - int32 delta; - } files[200]; - static int32 incstart; - static int32 nfunc, nfile, nhist; - Func *f; - int32 i, l; - - switch(sym->symtype) { - case 't': - case 'T': - if(hugestring.str == nil) - break; - if(runtime·strcmp(sym->name, (byte*)"etext") == 0) - break; - f = &func[nfunc++]; - // find source file - for(i = 0; i < nfile - 1; i++) { - if (files[i+1].aline > f->ln0) - break; - } - f->src = files[i].srcstring; - f->ln0 -= files[i].delta; - break; - case 'z': - if(sym->value == 1) { - // entry for main source file for a new object. - l = makepath(srcbuf, sizeof srcbuf, sym->name+1); - nhist = 0; - nfile = 0; - if(nfile == nelem(files)) - return; - files[nfile].srcstring = gostringn(srcbuf, l); - files[nfile].aline = 0; - files[nfile++].delta = 0; - } else { - // push or pop of included file. - l = makepath(srcbuf, sizeof srcbuf, sym->name+1); - if(srcbuf[0] != '\0') { - if(nhist++ == 0) - incstart = sym->value; - if(nhist == 0 && nfile < nelem(files)) { - // new top-level file - files[nfile].srcstring = gostringn(srcbuf, l); - files[nfile].aline = sym->value; - // this is "line 0" - files[nfile++].delta = sym->value - 1; - } - }else{ - if(--nhist == 0) - files[nfile-1].delta += sym->value - incstart; - } - } - } + if(f == nil || f->nameoff == 0) + return nil; + return (int8*)(pclntab + f->nameoff); } -// Interpret pc/ln table, saving the subpiece for each func. -static void -splitpcln(void) +static int32 +funcline(Func *f, uintptr targetpc, String *file, bool strict) { int32 line; - uintptr pc; - byte *p, *ep; - Func *f, *ef; - int32 pcquant; - - if(pclntab == epclntab || nfunc == 0) - return; - - switch(thechar) { - case '5': - pcquant = 4; - break; - default: // 6, 8 - pcquant = 1; - break; - } - - // pc/ln table bounds - p = pclntab; - ep = epclntab; - - f = func; - ef = func + nfunc; - pc = func[0].entry; // text base - f->pcln.array = p; - f->pc0 = pc; - line = 0; - for(;;) { - while(p < ep && *p > 128) - pc += pcquant * (*p++ - 128); - // runtime·printf("pc<%p targetpc=%p line=%d\n", pc, targetpc, line); - if(*p == 0) { - if(p+5 > ep) - break; - // 4 byte add to line - line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4]; - p += 5; - } else if(*p <= 64) - line += *p++; - else - line -= *p++ - 64; - - // pc, line now match. - // Because the state machine begins at pc==entry and line==0, - // it can happen - just at the beginning! - that the update may - // have updated line but left pc alone, to tell us the true line - // number for pc==entry. In that case, update f->ln0. - // Having the correct initial line number is important for choosing - // the correct file in dosrcline above. - if(f == func && pc == f->pc0) { - f->pcln.array = p; - f->pc0 = pc + pcquant; - f->ln0 = line; - } - - if(f < ef && pc >= (f+1)->entry) { - f->pcln.len = p - f->pcln.array; - f->pcln.cap = f->pcln.len; - do - f++; - while(f < ef && pc >= (f+1)->entry); - f->pcln.array = p; - // pc0 and ln0 are the starting values for - // the loop over f->pcln, so pc must be - // adjusted by the same pcquant update - // that we're going to do as we continue our loop. - f->pc0 = pc + pcquant; - f->ln0 = line; - } + int32 fileno; - pc += pcquant; - } - if(f < ef) { - f->pcln.len = p - f->pcln.array; - f->pcln.cap = f->pcln.len; + *file = unknown; + fileno = pcvalue(f, f->pcfile, targetpc, strict); + line = pcvalue(f, f->pcln, targetpc, strict); + if(fileno == -1 || line == -1 || fileno >= nfiletab) { + // runtime·printf("looking for %p in %S got file=%d line=%d\n", targetpc, *f->name, fileno, line); + return 0; } + *file = runtime·gostringnocopy(pclntab + filetab[fileno]); + return line; } - -// Return actual file line number for targetpc in func f. -// (Source file is f->src.) -// NOTE(rsc): If you edit this function, also edit extern.go:/FileLine int32 -runtime·funcline(Func *f, uintptr targetpc) +runtime·funcline(Func *f, uintptr targetpc, String *file) { - byte *p, *ep; - uintptr pc; - int32 line; - int32 pcquant; - - enum { - debug = 0 - }; + return funcline(f, targetpc, file, true); +} - switch(thechar) { - case '5': - pcquant = 4; - break; - default: // 6, 8 - pcquant = 1; - break; - } +int32 +runtime·funcspdelta(Func *f, uintptr targetpc) +{ + int32 x; + + x = pcvalue(f, f->pcsp, targetpc, true); + if(x&(sizeof(void*)-1)) + runtime·printf("invalid spdelta %d %d\n", f->pcsp, x); + return x; +} - p = f->pcln.array; - ep = p + f->pcln.len; - pc = f->pc0; - line = f->ln0; - if(debug && !runtime·panicking) - runtime·printf("funcline start pc=%p targetpc=%p line=%d tab=%p+%d\n", - pc, targetpc, line, p, (int32)f->pcln.len); - for(;;) { - // Table is a sequence of updates. - - // Each update says first how to adjust the pc, - // in possibly multiple instructions... - while(p < ep && *p > 128) - pc += pcquant * (*p++ - 128); - - if(debug && !runtime·panicking) - runtime·printf("pc<%p targetpc=%p line=%d\n", pc, targetpc, line); - - // If the pc has advanced too far or we're out of data, - // stop and the last known line number. - if(pc > targetpc || p >= ep) - break; +int32 +runtime·pcdatavalue(Func *f, int32 table, uintptr targetpc) +{ + if(table < 0 || table >= f->npcdata) + return -1; + return pcvalue(f, (&f->nfuncdata)[1+table], targetpc, true); +} - // ... and then how to adjust the line number, - // in a single instruction. - if(*p == 0) { - if(p+5 > ep) - break; - line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4]; - p += 5; - } else if(*p <= 64) - line += *p++; - else - line -= *p++ - 64; - // Now pc, line pair is consistent. - if(debug && !runtime·panicking) - runtime·printf("pc=%p targetpc=%p line=%d\n", pc, targetpc, line); - - // PC increments implicitly on each iteration. - pc += pcquant; - } - return line; +int32 +runtime·funcarglen(Func *f, uintptr targetpc) +{ + if(targetpc == f->entry) + return 0; + return runtime·pcdatavalue(f, PCDATA_ArgSize, targetpc-PCQuantum); } void runtime·funcline_go(Func *f, uintptr targetpc, String retfile, intgo retline) { - retfile = f->src; - retline = runtime·funcline(f, targetpc); - FLUSH(&retfile); + // Pass strict=false here, because anyone can call this function, + // and they might just be wrong about targetpc belonging to f. + retline = funcline(f, targetpc, &retfile, false); FLUSH(&retline); } -static void -buildfuncs(void) +void +runtime·funcname_go(Func *f, String ret) +{ + ret = runtime·gostringnocopy((uint8*)runtime·funcname(f)); + FLUSH(&ret); +} + +void +runtime·funcentry_go(Func *f, uintptr ret) { - extern byte etext[]; - - if(func != nil) - return; - - // Memory profiling uses this code; - // can deadlock if the profiler ends - // up back here. - m->nomemprof++; - - // count funcs, fnames - nfunc = 0; - nfname = 0; - lastvalue = 0; - walksymtab(dofunc); - - // Initialize tables. - // Can use FlagNoPointers - all pointers either point into sections of the executable - // or point into hugestring. - func = runtime·mallocgc((nfunc+1)*sizeof func[0], FlagNoPointers, 0, 1); - func[nfunc].entry = (uint64)etext; - fname = runtime·mallocgc(nfname*sizeof fname[0], FlagNoPointers, 0, 1); - nfunc = 0; - lastvalue = 0; - walksymtab(dofunc); - - // split pc/ln table by func - splitpcln(); - - // record src file and line info for each func - walksymtab(dosrcline); // pass 1: determine hugestring_len - hugestring.str = runtime·mallocgc(hugestring_len, FlagNoPointers, 0, 0); - hugestring.len = 0; - walksymtab(dosrcline); // pass 2: fill and use hugestring - - if(hugestring.len != hugestring_len) - runtime·throw("buildfunc: problem in initialization procedure"); - - m->nomemprof--; + ret = f->entry; + FLUSH(&ret); } Func* runtime·findfunc(uintptr addr) { - Func *f; + Ftab *f; int32 nf, n; - // Use atomic double-checked locking, - // because when called from pprof signal - // handler, findfunc must run without - // grabbing any locks. - // (Before enabling the signal handler, - // SetCPUProfileRate calls findfunc to trigger - // the initialization outside the handler.) - // Avoid deadlock on fault during malloc - // by not calling buildfuncs if we're already in malloc. - if(!m->mallocing && !m->gcing) { - if(runtime·atomicload(&funcinit) == 0) { - runtime·lock(&funclock); - if(funcinit == 0) { - buildfuncs(); - runtime·atomicstore(&funcinit, 1); - } - runtime·unlock(&funclock); - } - } - - if(nfunc == 0) + if(nftab == 0) return nil; - if(addr < func[0].entry || addr >= func[nfunc].entry) + if(addr < ftab[0].entry || addr >= ftab[nftab].entry) return nil; // binary search to find func with entry <= addr. - f = func; - nf = nfunc; + f = ftab; + nf = nftab; while(nf > 0) { n = nf/2; if(f[n].entry <= addr && addr < f[n+1].entry) - return &f[n]; + return (Func*)(pclntab + f[n].funcoff); else if(addr < f[n].entry) nf = n; else { @@ -663,13 +312,22 @@ contains(String s, int8 *p) } bool -runtime·showframe(Func *f, bool current) +runtime·showframe(Func *f, G *gp) { static int32 traceback = -1; + String name; - if(current && m->throwing > 0) + if(m->throwing > 0 && gp != nil && (gp == m->curg || gp == m->caughtsig)) return 1; if(traceback < 0) traceback = runtime·gotraceback(nil); - return traceback > 1 || f != nil && contains(f->name, ".") && !hasprefix(f->name, "runtime."); + name = runtime·gostringnocopy((uint8*)runtime·funcname(f)); + + // Special case: always show runtime.panic frame, so that we can + // see where a panic started in the middle of a stack trace. + // See golang.org/issue/5832. + if(name.len == 7+1+5 && hasprefix(name, "runtime.panic")) + return 1; + + return traceback > 1 || f != nil && contains(name, ".") && !hasprefix(name, "runtime."); } |