Changeset View
Changeset View
Standalone View
Standalone View
head/cddl/contrib/opensolaris/lib/libdtrace/common/dt_link.c
Show First 20 Lines • Show All 231 Lines • ▼ Show 20 Lines | |||||
printf("%s:%s(%d): DOODAD\n",__FUNCTION__,__FILE__,__LINE__); | printf("%s:%s(%d): DOODAD\n",__FUNCTION__,__FILE__,__LINE__); | ||||
#elif defined(__arm__) | #elif defined(__arm__) | ||||
/* XXX */ | /* XXX */ | ||||
printf("%s:%s(%d): DOODAD\n",__FUNCTION__,__FILE__,__LINE__); | printf("%s:%s(%d): DOODAD\n",__FUNCTION__,__FILE__,__LINE__); | ||||
#elif defined(__i386) || defined(__amd64) | #elif defined(__i386) || defined(__amd64) | ||||
rel->r_offset = s->dofs_offset + | rel->r_offset = s->dofs_offset + | ||||
dofr[j].dofr_offset; | dofr[j].dofr_offset; | ||||
rel->r_info = ELF32_R_INFO(count + dep->de_global, | rel->r_info = ELF32_R_INFO(count + dep->de_global, | ||||
R_386_32); | R_386_PC32); | ||||
#elif defined(__mips__) | #elif defined(__mips__) | ||||
/* XXX */ | /* XXX */ | ||||
printf("%s:%s(%d): DOODAD\n",__FUNCTION__,__FILE__,__LINE__); | printf("%s:%s(%d): DOODAD\n",__FUNCTION__,__FILE__,__LINE__); | ||||
#elif defined(__powerpc__) | #elif defined(__powerpc__) | ||||
/* | /* | ||||
* Add 4 bytes to hit the low half of this 64-bit | * Add 4 bytes to hit the low half of this 64-bit | ||||
* big-endian address. | * big-endian address. | ||||
*/ | */ | ||||
rel->r_offset = s->dofs_offset + | rel->r_offset = s->dofs_offset + | ||||
dofr[j].dofr_offset + 4; | dofr[j].dofr_offset + 4; | ||||
rel->r_info = ELF32_R_INFO(count + dep->de_global, | rel->r_info = ELF32_R_INFO(count + dep->de_global, | ||||
R_PPC_REL32); | R_PPC_REL32); | ||||
#elif defined(__riscv__) | #elif defined(__riscv__) | ||||
/* XXX */ | /* XXX */ | ||||
printf("%s:%s(%d): DOODAD\n",__FUNCTION__,__FILE__,__LINE__); | printf("%s:%s(%d): DOODAD\n",__FUNCTION__,__FILE__,__LINE__); | ||||
#elif defined(__sparc) | |||||
/* | |||||
* Add 4 bytes to hit the low half of this 64-bit | |||||
* big-endian address. | |||||
*/ | |||||
rel->r_offset = s->dofs_offset + | |||||
dofr[j].dofr_offset + 4; | |||||
rel->r_info = ELF32_R_INFO(count + dep->de_global, | |||||
R_SPARC_32); | |||||
#else | #else | ||||
#error unknown ISA | #error unknown ISA | ||||
#endif | #endif | ||||
sym->st_name = base + dofr[j].dofr_name - 1; | sym->st_name = base + dofr[j].dofr_name - 1; | ||||
sym->st_value = 0; | sym->st_value = 0; | ||||
sym->st_size = 0; | sym->st_size = 0; | ||||
sym->st_info = ELF32_ST_INFO(STB_GLOBAL, STT_FUNC); | sym->st_info = ELF32_ST_INFO(STB_GLOBAL, STT_FUNC); | ||||
sym->st_other = 0; | sym->st_other = ELF32_ST_VISIBILITY(STV_HIDDEN); | ||||
sym->st_shndx = SHN_UNDEF; | sym->st_shndx = SHN_UNDEF; | ||||
rel++; | rel++; | ||||
sym++; | sym++; | ||||
count++; | count++; | ||||
} | } | ||||
} | } | ||||
/* | /* | ||||
* Add a symbol for the DOF itself. We use a different symbol for | * Add a symbol for the DOF itself. We use a different symbol for | ||||
* lazily and actively loaded DOF to make them easy to distinguish. | * lazily and actively loaded DOF to make them easy to distinguish. | ||||
*/ | */ | ||||
sym->st_name = strtabsz; | sym->st_name = strtabsz; | ||||
sym->st_value = 0; | sym->st_value = 0; | ||||
sym->st_size = dof->dofh_filesz; | sym->st_size = dof->dofh_filesz; | ||||
sym->st_info = ELF32_ST_INFO(STB_GLOBAL, STT_OBJECT); | sym->st_info = ELF32_ST_INFO(STB_GLOBAL, STT_OBJECT); | ||||
#ifdef illumos | |||||
sym->st_other = 0; | |||||
#else | |||||
sym->st_other = ELF32_ST_VISIBILITY(STV_HIDDEN); | sym->st_other = ELF32_ST_VISIBILITY(STV_HIDDEN); | ||||
#endif | |||||
sym->st_shndx = ESHDR_DOF; | sym->st_shndx = ESHDR_DOF; | ||||
sym++; | sym++; | ||||
if (dtp->dt_lazyload) { | if (dtp->dt_lazyload) { | ||||
bcopy(DOFLAZYSTR, dep->de_strtab + strtabsz, | bcopy(DOFLAZYSTR, dep->de_strtab + strtabsz, | ||||
sizeof (DOFLAZYSTR)); | sizeof (DOFLAZYSTR)); | ||||
strtabsz += sizeof (DOFLAZYSTR); | strtabsz += sizeof (DOFLAZYSTR); | ||||
} else { | } else { | ||||
▲ Show 20 Lines • Show All 140 Lines • ▼ Show 20 Lines | #elif defined(__powerpc__) | ||||
dofr[j].dofr_offset; | dofr[j].dofr_offset; | ||||
rel->r_info = ELF64_R_INFO(count + dep->de_global, | rel->r_info = ELF64_R_INFO(count + dep->de_global, | ||||
R_PPC64_REL64); | R_PPC64_REL64); | ||||
#elif defined(__riscv__) | #elif defined(__riscv__) | ||||
/* XXX */ | /* XXX */ | ||||
#elif defined(__i386) || defined(__amd64) | #elif defined(__i386) || defined(__amd64) | ||||
rel->r_offset = s->dofs_offset + | rel->r_offset = s->dofs_offset + | ||||
dofr[j].dofr_offset; | dofr[j].dofr_offset; | ||||
#ifdef illumos | |||||
rel->r_info = ELF64_R_INFO(count + dep->de_global, | rel->r_info = ELF64_R_INFO(count + dep->de_global, | ||||
R_AMD64_64); | R_X86_64_PC64); | ||||
#else | #else | ||||
rel->r_info = ELF64_R_INFO(count + dep->de_global, | |||||
R_X86_64_RELATIVE); | |||||
#endif | |||||
#elif defined(__sparc) | |||||
rel->r_offset = s->dofs_offset + | |||||
dofr[j].dofr_offset; | |||||
rel->r_info = ELF64_R_INFO(count + dep->de_global, | |||||
R_SPARC_64); | |||||
#else | |||||
#error unknown ISA | #error unknown ISA | ||||
#endif | #endif | ||||
sym->st_name = base + dofr[j].dofr_name - 1; | sym->st_name = base + dofr[j].dofr_name - 1; | ||||
sym->st_value = 0; | sym->st_value = 0; | ||||
sym->st_size = 0; | sym->st_size = 0; | ||||
sym->st_info = GELF_ST_INFO(STB_GLOBAL, STT_FUNC); | sym->st_info = GELF_ST_INFO(STB_GLOBAL, STT_FUNC); | ||||
sym->st_other = 0; | sym->st_other = ELF64_ST_VISIBILITY(STV_HIDDEN); | ||||
sym->st_shndx = SHN_UNDEF; | sym->st_shndx = SHN_UNDEF; | ||||
rel++; | rel++; | ||||
sym++; | sym++; | ||||
count++; | count++; | ||||
} | } | ||||
} | } | ||||
/* | /* | ||||
* Add a symbol for the DOF itself. We use a different symbol for | * Add a symbol for the DOF itself. We use a different symbol for | ||||
* lazily and actively loaded DOF to make them easy to distinguish. | * lazily and actively loaded DOF to make them easy to distinguish. | ||||
*/ | */ | ||||
sym->st_name = strtabsz; | sym->st_name = strtabsz; | ||||
sym->st_value = 0; | sym->st_value = 0; | ||||
sym->st_size = dof->dofh_filesz; | sym->st_size = dof->dofh_filesz; | ||||
sym->st_info = GELF_ST_INFO(STB_GLOBAL, STT_OBJECT); | sym->st_info = GELF_ST_INFO(STB_GLOBAL, STT_OBJECT); | ||||
#ifdef illumos | |||||
sym->st_other = 0; | |||||
#else | |||||
sym->st_other = ELF64_ST_VISIBILITY(STV_HIDDEN); | sym->st_other = ELF64_ST_VISIBILITY(STV_HIDDEN); | ||||
#endif | |||||
sym->st_shndx = ESHDR_DOF; | sym->st_shndx = ESHDR_DOF; | ||||
sym++; | sym++; | ||||
if (dtp->dt_lazyload) { | if (dtp->dt_lazyload) { | ||||
bcopy(DOFLAZYSTR, dep->de_strtab + strtabsz, | bcopy(DOFLAZYSTR, dep->de_strtab + strtabsz, | ||||
sizeof (DOFLAZYSTR)); | sizeof (DOFLAZYSTR)); | ||||
strtabsz += sizeof (DOFLAZYSTR); | strtabsz += sizeof (DOFLAZYSTR); | ||||
} else { | } else { | ||||
▲ Show 20 Lines • Show All 291 Lines • ▼ Show 20 Lines | #endif | ||||
free(de.de_strtab); | free(de.de_strtab); | ||||
free(de.de_sym); | free(de.de_sym); | ||||
free(de.de_rel); | free(de.de_rel); | ||||
return (ret); | return (ret); | ||||
} | } | ||||
static int | static int | ||||
dt_symtab_lookup(Elf_Data *data_sym, int nsym, uintptr_t addr, uint_t shn, | dt_symtab_lookup(Elf_Data *data_sym, int start, int end, uintptr_t addr, | ||||
GElf_Sym *sym, int uses_funcdesc, Elf *elf) | uint_t shn, GElf_Sym *sym, int uses_funcdesc, Elf *elf) | ||||
{ | { | ||||
int i, ret = -1; | |||||
Elf64_Addr symval; | Elf64_Addr symval; | ||||
Elf_Scn *opd_scn; | Elf_Scn *opd_scn; | ||||
Elf_Data *opd_desc; | Elf_Data *opd_desc; | ||||
GElf_Sym s; | int i; | ||||
for (i = 0; i < nsym && gelf_getsym(data_sym, i, sym) != NULL; i++) { | for (i = start; i < end && gelf_getsym(data_sym, i, sym) != NULL; i++) { | ||||
if (GELF_ST_TYPE(sym->st_info) == STT_FUNC) { | if (GELF_ST_TYPE(sym->st_info) == STT_FUNC) { | ||||
symval = sym->st_value; | symval = sym->st_value; | ||||
if (uses_funcdesc) { | if (uses_funcdesc) { | ||||
opd_scn = elf_getscn(elf, sym->st_shndx); | opd_scn = elf_getscn(elf, sym->st_shndx); | ||||
opd_desc = elf_rawdata(opd_scn, NULL); | opd_desc = elf_rawdata(opd_scn, NULL); | ||||
symval = | symval = | ||||
*(uint64_t*)((char *)opd_desc->d_buf + symval); | *(uint64_t*)((char *)opd_desc->d_buf + symval); | ||||
} | } | ||||
if ((uses_funcdesc || shn == sym->st_shndx) && | if ((uses_funcdesc || shn == sym->st_shndx) && | ||||
symval <= addr && | symval <= addr && addr < symval + sym->st_size) | ||||
addr < symval + sym->st_size) { | |||||
if (GELF_ST_BIND(sym->st_info) == STB_GLOBAL) | |||||
return (0); | return (0); | ||||
ret = 0; | |||||
s = *sym; | |||||
} | } | ||||
} | } | ||||
} | |||||
if (ret == 0) | return (-1); | ||||
*sym = s; | |||||
return (ret); | |||||
} | } | ||||
#if defined(__aarch64__) | #if defined(__aarch64__) | ||||
/* XXX */ | /* XXX */ | ||||
static int | static int | ||||
dt_modtext(dtrace_hdl_t *dtp, char *p, int isenabled, GElf_Rela *rela, | dt_modtext(dtrace_hdl_t *dtp, char *p, int isenabled, GElf_Rela *rela, | ||||
uint32_t *off) | uint32_t *off) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 391 Lines • ▼ Show 20 Lines | process_obj(dtrace_hdl_t *dtp, const char *obj, int *eprobesp) | ||||
GElf_Shdr shdr_rel, shdr_sym, shdr_str, shdr_tgt; | GElf_Shdr shdr_rel, shdr_sym, shdr_str, shdr_tgt; | ||||
GElf_Sym rsym, fsym, dsym; | GElf_Sym rsym, fsym, dsym; | ||||
GElf_Rela rela; | GElf_Rela rela; | ||||
char *s, *p, *r; | char *s, *p, *r; | ||||
char pname[DTRACE_PROVNAMELEN]; | char pname[DTRACE_PROVNAMELEN]; | ||||
dt_provider_t *pvp; | dt_provider_t *pvp; | ||||
dt_probe_t *prp; | dt_probe_t *prp; | ||||
uint32_t off, eclass, emachine1, emachine2; | uint32_t off, eclass, emachine1, emachine2; | ||||
size_t symsize, nsym, isym, istr, len; | size_t symsize, osym, nsym, isym, istr, len; | ||||
key_t objkey; | key_t objkey; | ||||
dt_link_pair_t *pair, *bufs = NULL; | dt_link_pair_t *pair, *bufs = NULL; | ||||
dt_strtab_t *strtab; | dt_strtab_t *strtab; | ||||
if ((fd = open64(obj, O_RDWR)) == -1) { | if ((fd = open64(obj, O_RDWR)) == -1) { | ||||
return (dt_link_error(dtp, elf, fd, bufs, | return (dt_link_error(dtp, elf, fd, bufs, | ||||
"failed to open %s: %s", obj, strerror(errno))); | "failed to open %s: %s", obj, strerror(errno))); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 120 Lines • ▼ Show 20 Lines | while ((scn_rel = elf_nextscn(elf, scn_rel)) != NULL) { | ||||
* identified by the relocation, and create a new relocation | * identified by the relocation, and create a new relocation | ||||
* in the generated object that will be resolved at link time | * in the generated object that will be resolved at link time | ||||
* to the location of the function in which the probe is | * to the location of the function in which the probe is | ||||
* embedded. In the target object, we change the matched symbol | * embedded. In the target object, we change the matched symbol | ||||
* so that it will be ignored at link time, and we modify the | * so that it will be ignored at link time, and we modify the | ||||
* target (text) section to replace the call instruction with | * target (text) section to replace the call instruction with | ||||
* one or more nops. | * one or more nops. | ||||
* | * | ||||
* If the function containing the probe is locally scoped | * To avoid runtime overhead, the relocations added to the | ||||
* (static), we create an alias used by the relocation in the | * generated object should be resolved at static link time. We | ||||
* generated object. The alias, a new symbol, will be global | * therefore create aliases for the functions that contain | ||||
* (so that the relocation from the generated object can be | * probes. An alias is global (so that the relocation from the | ||||
* resolved), and hidden (so that it is converted to a local | * generated object can be resolved), and hidden (so that its | ||||
* symbol at link time). Such aliases have this form: | * address is known at static link time). Such aliases have this | ||||
* form: | |||||
* | * | ||||
* $dtrace<key>.<function> | * $dtrace<key>.<function> | ||||
* | * | ||||
* We take a first pass through all the relocations to | * We take a first pass through all the relocations to | ||||
* populate our string table and count the number of extra | * populate our string table and count the number of extra | ||||
* symbols we'll require. | * symbols we'll require. | ||||
*/ | */ | ||||
strtab = dt_strtab_create(1); | strtab = dt_strtab_create(1); | ||||
Show All 21 Lines | for (i = 0; i < shdr_rel.sh_size / shdr_rel.sh_entsize; i++) { | ||||
goto err; | goto err; | ||||
} | } | ||||
s = (char *)data_str->d_buf + rsym.st_name; | s = (char *)data_str->d_buf + rsym.st_name; | ||||
if (strncmp(s, dt_prefix, sizeof (dt_prefix) - 1) != 0) | if (strncmp(s, dt_prefix, sizeof (dt_prefix) - 1) != 0) | ||||
continue; | continue; | ||||
if (dt_symtab_lookup(data_sym, isym, rela.r_offset, | if (dt_symtab_lookup(data_sym, 0, isym, rela.r_offset, | ||||
shdr_rel.sh_info, &fsym, | shdr_rel.sh_info, &fsym, (emachine1 == EM_PPC64), | ||||
(emachine1 == EM_PPC64), elf) != 0) { | elf) != 0) { | ||||
dt_strtab_destroy(strtab); | dt_strtab_destroy(strtab); | ||||
goto err; | goto err; | ||||
} | } | ||||
if (GELF_ST_BIND(fsym.st_info) != STB_LOCAL) | |||||
continue; | |||||
if (fsym.st_name > data_str->d_size) { | if (fsym.st_name > data_str->d_size) { | ||||
dt_strtab_destroy(strtab); | dt_strtab_destroy(strtab); | ||||
goto err; | goto err; | ||||
} | } | ||||
s = (char *)data_str->d_buf + fsym.st_name; | s = (char *)data_str->d_buf + fsym.st_name; | ||||
/* | /* | ||||
Show All 19 Lines | for (i = 0; i < shdr_rel.sh_size / shdr_rel.sh_entsize; i++) { | ||||
nsym++; | nsym++; | ||||
(void) dt_strtab_insert(strtab, p); | (void) dt_strtab_insert(strtab, p); | ||||
} | } | ||||
dt_free(dtp, p); | dt_free(dtp, p); | ||||
} | } | ||||
/* | /* | ||||
* If needed, allocate the additional space for the symbol | * If any probes were found, allocate the additional space for | ||||
* table and string table copying the old data into the new | * the symbol table and string table, copying the old data into | ||||
* buffers, and marking the buffers as dirty. We inject those | * the new buffers, and marking the buffers as dirty. We inject | ||||
* newly allocated buffers into the libelf data structures, but | * those newly allocated buffers into the libelf data | ||||
* are still responsible for freeing them once we're done with | * structures, but are still responsible for freeing them once | ||||
* the elf handle. | * we're done with the elf handle. | ||||
*/ | */ | ||||
if (nsym > 0) { | if (nsym > 0) { | ||||
/* | /* | ||||
* The first byte of the string table is reserved for | * The first byte of the string table is reserved for | ||||
* the \0 entry. | * the \0 entry. | ||||
*/ | */ | ||||
len = dt_strtab_size(strtab) - 1; | len = dt_strtab_size(strtab) - 1; | ||||
Show All 32 Lines | if (nsym > 0) { | ||||
bcopy(data_sym->d_buf, pair->dlp_sym, data_sym->d_size); | bcopy(data_sym->d_buf, pair->dlp_sym, data_sym->d_size); | ||||
data_sym->d_buf = pair->dlp_sym; | data_sym->d_buf = pair->dlp_sym; | ||||
data_sym->d_size += nsym * symsize; | data_sym->d_size += nsym * symsize; | ||||
(void) elf_flagdata(data_sym, ELF_C_SET, ELF_F_DIRTY); | (void) elf_flagdata(data_sym, ELF_C_SET, ELF_F_DIRTY); | ||||
shdr_sym.sh_size += nsym * symsize; | shdr_sym.sh_size += nsym * symsize; | ||||
(void) gelf_update_shdr(scn_sym, &shdr_sym); | (void) gelf_update_shdr(scn_sym, &shdr_sym); | ||||
osym = isym; | |||||
nsym += isym; | nsym += isym; | ||||
} else { | } else { | ||||
dt_strtab_destroy(strtab); | dt_strtab_destroy(strtab); | ||||
continue; | |||||
} | } | ||||
/* | /* | ||||
* Now that the tables have been allocated, perform the | * Now that the tables have been allocated, perform the | ||||
* modifications described above. | * modifications described above. | ||||
*/ | */ | ||||
for (i = 0; i < shdr_rel.sh_size / shdr_rel.sh_entsize; i++) { | for (i = 0; i < shdr_rel.sh_size / shdr_rel.sh_entsize; i++) { | ||||
▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | for (i = 0; i < shdr_rel.sh_size / shdr_rel.sh_entsize; i++) { | ||||
if ((p = strstr(s, "___")) == NULL || | if ((p = strstr(s, "___")) == NULL || | ||||
p - s >= sizeof (pname)) | p - s >= sizeof (pname)) | ||||
goto err; | goto err; | ||||
bcopy(s, pname, p - s); | bcopy(s, pname, p - s); | ||||
pname[p - s] = '\0'; | pname[p - s] = '\0'; | ||||
if (dt_symtab_lookup(data_sym, isym, rela.r_offset, | if (dt_symtab_lookup(data_sym, osym, isym, | ||||
shdr_rel.sh_info, &fsym, | rela.r_offset, shdr_rel.sh_info, &fsym, | ||||
(emachine1 == EM_PPC64), elf) != 0 && | |||||
dt_symtab_lookup(data_sym, 0, osym, | |||||
rela.r_offset, shdr_rel.sh_info, &fsym, | |||||
(emachine1 == EM_PPC64), elf) != 0) | (emachine1 == EM_PPC64), elf) != 0) | ||||
goto err; | goto err; | ||||
if (fsym.st_name > data_str->d_size) | if (fsym.st_name > data_str->d_size) | ||||
goto err; | goto err; | ||||
assert(GELF_ST_TYPE(fsym.st_info) == STT_FUNC); | assert(GELF_ST_TYPE(fsym.st_info) == STT_FUNC); | ||||
/* | /* | ||||
* If a NULL relocation name is passed to | * If this is our first time encountering this symbol, | ||||
* dt_probe_define(), the function name is used for the | * emit an alias. | ||||
* relocation. The relocation needs to use a mangled | |||||
* name if the symbol is locally scoped; the function | |||||
* name may need to change if we've found the global | |||||
* alias for the locally scoped symbol (we prefer | |||||
* global symbols to locals in dt_symtab_lookup()). | |||||
*/ | */ | ||||
s = (char *)data_str->d_buf + fsym.st_name; | s = (char *)data_str->d_buf + fsym.st_name; | ||||
r = NULL; | |||||
if (GELF_ST_BIND(fsym.st_info) == STB_LOCAL) { | if (strncmp(s, dt_symprefix, | ||||
sizeof (dt_symprefix) - 1) != 0) { | |||||
u_int bind = GELF_ST_BIND(fsym.st_info); | |||||
dsym = fsym; | dsym = fsym; | ||||
dsym.st_name = istr; | dsym.st_name = istr; | ||||
dsym.st_info = GELF_ST_INFO(STB_GLOBAL, | dsym.st_info = GELF_ST_INFO(bind == STB_LOCAL ? | ||||
STT_FUNC); | STB_GLOBAL : bind, STT_FUNC); | ||||
dsym.st_other = | dsym.st_other = GELF_ST_VISIBILITY(STV_HIDDEN); | ||||
ELF64_ST_VISIBILITY(STV_ELIMINATE); | |||||
(void) gelf_update_sym(data_sym, isym, &dsym); | (void) gelf_update_sym(data_sym, isym, &dsym); | ||||
r = (char *)data_str->d_buf + istr; | r = (char *) data_str->d_buf + istr; | ||||
istr += 1 + sprintf(r, dt_symfmt, | istr += 1 + sprintf(r, dt_symfmt, dt_symprefix, objkey, | ||||
dt_symprefix, objkey, s); | s); | ||||
isym++; | isym++; | ||||
assert(isym <= nsym); | assert(isym <= nsym); | ||||
} else { | |||||
} else if (strncmp(s, dt_symprefix, | |||||
strlen(dt_symprefix)) == 0) { | |||||
r = s; | r = s; | ||||
if ((s = strchr(s, '.')) == NULL) | s = strchr(s, '.'); | ||||
goto err; | assert(s != NULL); | ||||
s++; | s++; | ||||
} | } | ||||
if ((pvp = dt_provider_lookup(dtp, pname)) == NULL) { | if ((pvp = dt_provider_lookup(dtp, pname)) == NULL) { | ||||
return (dt_link_error(dtp, elf, fd, bufs, | return (dt_link_error(dtp, elf, fd, bufs, | ||||
"no such provider %s", pname)); | "no such provider %s", pname)); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 354 Lines • Show Last 20 Lines |