Commit b83d4af3 authored by Russ Cox's avatar Russ Cox

cmd/ld: make mach-o sections match internal sections

This brings Mach-O generation more in line with ELF generation.

Having separate sections for the symtab and pclntab mean that we
can find them that way, instead of using the deprecated debug segments.
(And the host linker will keep separate sections for us, but probably
not the debug segments.)

R=ken2
CC=golang-dev
https://golang.org/cl/7688043
parent e982ecac
...@@ -879,6 +879,9 @@ symalign(Sym *s) ...@@ -879,6 +879,9 @@ symalign(Sym *s)
{ {
int32 align; int32 align;
if(s->align != 0)
return s->align;
align = MaxAlign; align = MaxAlign;
while(align > s->size && align > 1) while(align > s->size && align > 1)
align >>= 1; align >>= 1;
...@@ -1136,9 +1139,10 @@ dodata(void) ...@@ -1136,9 +1139,10 @@ dodata(void)
lookup("end", 0)->sect = sect; lookup("end", 0)->sect = sect;
/* we finished segdata, begin segtext */ /* we finished segdata, begin segtext */
s = datap;
datsize = 0;
/* read-only data */ /* read-only data */
s = datap;
sect = addsection(&segtext, ".rodata", 04); sect = addsection(&segtext, ".rodata", 04);
sect->align = maxalign(s, STYPELINK-1); sect->align = maxalign(s, STYPELINK-1);
sect->vaddr = 0; sect->vaddr = 0;
...@@ -1202,7 +1206,7 @@ dodata(void) ...@@ -1202,7 +1206,7 @@ dodata(void)
} }
sect->len = datsize - sect->vaddr; sect->len = datsize - sect->vaddr;
/* read-only ELF sections */ /* read-only ELF, Mach-O sections */
for(; s != nil && s->type < SELFSECT; s = s->next) { for(; s != nil && s->type < SELFSECT; s = s->next) {
sect = addsection(&segtext, s->name, 04); sect = addsection(&segtext, s->name, 04);
sect->align = symalign(s); sect->align = symalign(s);
...@@ -1266,6 +1270,7 @@ address(void) ...@@ -1266,6 +1270,7 @@ address(void)
Section *typelink; Section *typelink;
Sym *sym, *sub; Sym *sym, *sub;
uvlong va; uvlong va;
vlong vlen;
va = INITTEXT; va = INITTEXT;
segtext.rwx = 05; segtext.rwx = 05;
...@@ -1295,11 +1300,11 @@ address(void) ...@@ -1295,11 +1300,11 @@ address(void)
noptrbss = nil; noptrbss = nil;
datarelro = nil; datarelro = nil;
for(s=segdata.sect; s != nil; s=s->next) { for(s=segdata.sect; s != nil; s=s->next) {
vlen = s->len;
if(s->next) if(s->next)
s->len = s->next->vaddr - s->vaddr; vlen = s->next->vaddr - s->vaddr;
s->vaddr = va; s->vaddr = va;
va += s->len; va += vlen;
segdata.filelen += s->len;
segdata.len = va - segdata.vaddr; segdata.len = va - segdata.vaddr;
if(strcmp(s->name, ".data") == 0) if(strcmp(s->name, ".data") == 0)
data = s; data = s;
......
...@@ -2298,42 +2298,42 @@ dwarfaddmachoheaders(void) ...@@ -2298,42 +2298,42 @@ dwarfaddmachoheaders(void)
ms->fileoffset = fakestart; ms->fileoffset = fakestart;
ms->filesize = abbrevo-fakestart; ms->filesize = abbrevo-fakestart;
msect = newMachoSect(ms, "__debug_abbrev"); msect = newMachoSect(ms, "__debug_abbrev", "__DWARF");
msect->off = abbrevo; msect->off = abbrevo;
msect->size = abbrevsize; msect->size = abbrevsize;
ms->filesize += msect->size; ms->filesize += msect->size;
msect = newMachoSect(ms, "__debug_line"); msect = newMachoSect(ms, "__debug_line", "__DWARF");
msect->off = lineo; msect->off = lineo;
msect->size = linesize; msect->size = linesize;
ms->filesize += msect->size; ms->filesize += msect->size;
msect = newMachoSect(ms, "__debug_frame"); msect = newMachoSect(ms, "__debug_frame", "__DWARF");
msect->off = frameo; msect->off = frameo;
msect->size = framesize; msect->size = framesize;
ms->filesize += msect->size; ms->filesize += msect->size;
msect = newMachoSect(ms, "__debug_info"); msect = newMachoSect(ms, "__debug_info", "__DWARF");
msect->off = infoo; msect->off = infoo;
msect->size = infosize; msect->size = infosize;
ms->filesize += msect->size; ms->filesize += msect->size;
if (pubnamessize > 0) { if (pubnamessize > 0) {
msect = newMachoSect(ms, "__debug_pubnames"); msect = newMachoSect(ms, "__debug_pubnames", "__DWARF");
msect->off = pubnameso; msect->off = pubnameso;
msect->size = pubnamessize; msect->size = pubnamessize;
ms->filesize += msect->size; ms->filesize += msect->size;
} }
if (pubtypessize > 0) { if (pubtypessize > 0) {
msect = newMachoSect(ms, "__debug_pubtypes"); msect = newMachoSect(ms, "__debug_pubtypes", "__DWARF");
msect->off = pubtypeso; msect->off = pubtypeso;
msect->size = pubtypessize; msect->size = pubtypessize;
ms->filesize += msect->size; ms->filesize += msect->size;
} }
if (arangessize > 0) { if (arangessize > 0) {
msect = newMachoSect(ms, "__debug_aranges"); msect = newMachoSect(ms, "__debug_aranges", "__DWARF");
msect->off = arangeso; msect->off = arangeso;
msect->size = arangessize; msect->size = arangessize;
ms->filesize += msect->size; ms->filesize += msect->size;
...@@ -2341,7 +2341,7 @@ dwarfaddmachoheaders(void) ...@@ -2341,7 +2341,7 @@ dwarfaddmachoheaders(void)
// TODO(lvd) fix gdb/python to load MachO (16 char section name limit) // TODO(lvd) fix gdb/python to load MachO (16 char section name limit)
if (gdbscriptsize > 0) { if (gdbscriptsize > 0) {
msect = newMachoSect(ms, "__debug_gdb_scripts"); msect = newMachoSect(ms, "__debug_gdb_scripts", "__DWARF");
msect->off = gdbscripto; msect->off = gdbscripto;
msect->size = gdbscriptsize; msect->size = gdbscriptsize;
ms->filesize += msect->size; ms->filesize += msect->size;
......
...@@ -34,7 +34,6 @@ enum ...@@ -34,7 +34,6 @@ enum
/* order here is order in output file */ /* order here is order in output file */
STEXT, STEXT,
SMACHOPLT,
STYPE, STYPE,
SSTRING, SSTRING,
SGOSTRING, SGOSTRING,
...@@ -43,12 +42,13 @@ enum ...@@ -43,12 +42,13 @@ enum
SSYMTAB, SSYMTAB,
SPCLNTAB, SPCLNTAB,
SELFROSECT, SELFROSECT,
SMACHOPLT,
SELFSECT, SELFSECT,
SMACHO, /* Mach-O __nl_symbol_ptr */
SMACHOGOT,
SNOPTRDATA, SNOPTRDATA,
SDATARELRO, SDATARELRO,
SDATA, SDATA,
SMACHO, /* Mach-O __nl_symbol_ptr */
SMACHOGOT,
SWINDOWS, SWINDOWS,
SBSS, SBSS,
SNOPTRBSS, SNOPTRBSS,
......
...@@ -14,7 +14,6 @@ static int macho64; ...@@ -14,7 +14,6 @@ static int macho64;
static MachoHdr hdr; static MachoHdr hdr;
static MachoLoad *load; static MachoLoad *load;
static MachoSeg seg[16]; static MachoSeg seg[16];
static MachoDebug xdebug[16];
static int nload, mload, nseg, ndebug, nsect; static int nload, mload, nseg, ndebug, nsect;
// Amount of space left for adding load commands // Amount of space left for adding load commands
...@@ -86,7 +85,7 @@ newMachoSeg(char *name, int msect) ...@@ -86,7 +85,7 @@ newMachoSeg(char *name, int msect)
} }
MachoSect* MachoSect*
newMachoSect(MachoSeg *seg, char *name) newMachoSect(MachoSeg *seg, char *name, char *segname)
{ {
MachoSect *s; MachoSect *s;
...@@ -96,21 +95,11 @@ newMachoSect(MachoSeg *seg, char *name) ...@@ -96,21 +95,11 @@ newMachoSect(MachoSeg *seg, char *name)
} }
s = &seg->sect[seg->nsect++]; s = &seg->sect[seg->nsect++];
s->name = name; s->name = name;
s->segname = segname;
nsect++; nsect++;
return s; return s;
} }
MachoDebug*
newMachoDebug(void)
{
if(ndebug >= nelem(xdebug)) {
diag("too many debugs");
errorexit();
}
return &xdebug[ndebug++];
}
// Generic linking code. // Generic linking code.
static char **dylib; static char **dylib;
...@@ -126,7 +115,6 @@ machowrite(void) ...@@ -126,7 +115,6 @@ machowrite(void)
int i, j; int i, j;
MachoSeg *s; MachoSeg *s;
MachoSect *t; MachoSect *t;
MachoDebug *d;
MachoLoad *l; MachoLoad *l;
o1 = cpos(); o1 = cpos();
...@@ -186,7 +174,7 @@ machowrite(void) ...@@ -186,7 +174,7 @@ machowrite(void)
t = &s->sect[j]; t = &s->sect[j];
if(macho64) { if(macho64) {
strnput(t->name, 16); strnput(t->name, 16);
strnput(s->name, 16); strnput(t->segname, 16);
VPUT(t->addr); VPUT(t->addr);
VPUT(t->size); VPUT(t->size);
LPUT(t->off); LPUT(t->off);
...@@ -199,7 +187,7 @@ machowrite(void) ...@@ -199,7 +187,7 @@ machowrite(void)
LPUT(0); /* reserved */ LPUT(0); /* reserved */
} else { } else {
strnput(t->name, 16); strnput(t->name, 16);
strnput(s->name, 16); strnput(t->segname, 16);
LPUT(t->addr); LPUT(t->addr);
LPUT(t->size); LPUT(t->size);
LPUT(t->off); LPUT(t->off);
...@@ -221,14 +209,6 @@ machowrite(void) ...@@ -221,14 +209,6 @@ machowrite(void)
LPUT(l->data[j]); LPUT(l->data[j]);
} }
for(i=0; i<ndebug; i++) {
d = &xdebug[i];
LPUT(3); /* obsolete gdb debug info */
LPUT(16); /* size of symseg command */
LPUT(d->fileoffset);
LPUT(d->filesize);
}
return cpos() - o1; return cpos() - o1;
} }
...@@ -258,6 +238,7 @@ domacho(void) ...@@ -258,6 +238,7 @@ domacho(void)
s = lookup(".got", 0); // will be __nl_symbol_ptr s = lookup(".got", 0); // will be __nl_symbol_ptr
s->type = SMACHOGOT; s->type = SMACHOGOT;
s->reachable = 1; s->reachable = 1;
s->align = 4;
s = lookup(".linkedit.plt", 0); // indirect table for .plt s = lookup(".linkedit.plt", 0); // indirect table for .plt
s->type = SMACHOINDIRECTPLT; s->type = SMACHOINDIRECTPLT;
...@@ -287,6 +268,52 @@ machoadddynlib(char *lib) ...@@ -287,6 +268,52 @@ machoadddynlib(char *lib)
dylib[ndylib++] = lib; dylib[ndylib++] = lib;
} }
static void
machoshbits(MachoSeg *mseg, Section *sect, char *segname)
{
MachoSect *msect;
char buf[40];
char *p;
snprint(buf, sizeof buf, "__%s", sect->name+1);
for(p=buf; *p; p++)
if(*p == '.')
*p = '_';
msect = newMachoSect(mseg, estrdup(buf), segname);
while(1<<msect->align < sect->align)
msect->align++;
msect->addr = sect->vaddr;
msect->size = sect->len;
msect->off = sect->seg->fileoff + sect->vaddr - sect->seg->vaddr;
if(sect->vaddr < sect->seg->vaddr + sect->seg->filelen) {
// data in file
if(sect->len > sect->seg->vaddr + sect->seg->filelen - sect->vaddr)
diag("macho cannot represent section %s crossing data and bss", sect->name);
} else {
// zero fill
msect->flag |= 1;
}
if(sect->rwx & 1)
msect->flag |= 0x400; /* has instructions */
if(strcmp(sect->name, ".plt") == 0) {
msect->name = "__symbol_stub1";
msect->flag = 0x80000408; /* only instructions, code, symbol stubs */
msect->res1 = 0;
msect->res2 = 6;
}
if(strcmp(sect->name, ".got") == 0) {
msect->name = "__nl_symbol_ptr";
msect->flag = 6; /* section with nonlazy symbol pointers */
msect->res1 = lookup(".linkedit.plt", 0)->size / 4; /* offset into indirect symbol table */
}
}
void void
asmbmacho(void) asmbmacho(void)
{ {
...@@ -294,11 +321,9 @@ asmbmacho(void) ...@@ -294,11 +321,9 @@ asmbmacho(void)
vlong va; vlong va;
int a, i; int a, i;
MachoHdr *mh; MachoHdr *mh;
MachoSect *msect;
MachoSeg *ms; MachoSeg *ms;
MachoDebug *md;
MachoLoad *ml; MachoLoad *ml;
Sym *s; Section *sect;
/* apple MACH */ /* apple MACH */
va = INITTEXT - HEADR; va = INITTEXT - HEADR;
...@@ -323,62 +348,29 @@ asmbmacho(void) ...@@ -323,62 +348,29 @@ asmbmacho(void)
/* text */ /* text */
v = rnd(HEADR+segtext.len, INITRND); v = rnd(HEADR+segtext.len, INITRND);
ms = newMachoSeg("__TEXT", 2); ms = newMachoSeg("__TEXT", 20);
ms->vaddr = va; ms->vaddr = va;
ms->vsize = v; ms->vsize = v;
ms->fileoffset = 0;
ms->filesize = v; ms->filesize = v;
ms->prot1 = 7; ms->prot1 = 7;
ms->prot2 = 5; ms->prot2 = 5;
msect = newMachoSect(ms, "__text");
msect->addr = INITTEXT;
msect->size = segtext.sect->len;
msect->off = INITTEXT - va;
msect->flag = 0x400; /* flag - some instructions */
s = lookup(".plt", 0); for(sect=segtext.sect; sect!=nil; sect=sect->next)
if(s->size > 0) { machoshbits(ms, sect, "__TEXT");
msect = newMachoSect(ms, "__symbol_stub1");
msect->addr = symaddr(s);
msect->size = s->size;
msect->off = ms->fileoffset + msect->addr - ms->vaddr;
msect->flag = 0x80000408; /* flag */
msect->res1 = 0; /* index into indirect symbol table */
msect->res2 = 6; /* size of stubs */
}
/* data */ /* data */
w = segdata.len; w = segdata.len;
ms = newMachoSeg("__DATA", 3); ms = newMachoSeg("__DATA", 20);
ms->vaddr = va+v; ms->vaddr = va+v;
ms->vsize = w; ms->vsize = w;
ms->fileoffset = v; ms->fileoffset = v;
ms->filesize = segdata.filelen; ms->filesize = segdata.filelen;
ms->prot1 = 7; ms->prot1 = 3;
ms->prot2 = 3; ms->prot2 = 3;
msect = newMachoSect(ms, "__data"); for(sect=segdata.sect; sect!=nil; sect=sect->next)
msect->addr = va+v; machoshbits(ms, sect, "__DATA");
msect->off = v;
msect->size = segdata.filelen;
s = lookup(".got", 0);
if(s->size > 0) {
msect->size = symaddr(s) - msect->addr;
msect = newMachoSect(ms, "__nl_symbol_ptr");
msect->addr = symaddr(s);
msect->size = s->size;
msect->off = datoff(msect->addr);
msect->align = 2;
msect->flag = 6; /* section with nonlazy symbol pointers */
msect->res1 = lookup(".linkedit.plt", 0)->size / 4; /* offset into indirect symbol table */
}
msect = newMachoSect(ms, "__bss");
msect->addr = va+v+segdata.filelen;
msect->size = segdata.len - segdata.filelen;
msect->flag = 1; /* flag - zero fill */
switch(thechar) { switch(thechar) {
default: default:
...@@ -456,21 +448,8 @@ asmbmacho(void) ...@@ -456,21 +448,8 @@ asmbmacho(void)
} }
} }
if(!debug['s']) { if(!debug['s'])
Sym *s;
md = newMachoDebug();
s = lookup("symtab", 0);
md->fileoffset = datoff(s->value);
md->filesize = s->size;
md = newMachoDebug();
s = lookup("pclntab", 0);
md->fileoffset = datoff(s->value);
md->filesize = s->size;
dwarfaddmachoheaders(); dwarfaddmachoheaders();
}
a = machowrite(); a = machowrite();
if(a > HEADR) if(a > HEADR)
......
...@@ -11,6 +11,7 @@ struct MachoHdr { ...@@ -11,6 +11,7 @@ struct MachoHdr {
typedef struct MachoSect MachoSect; typedef struct MachoSect MachoSect;
struct MachoSect { struct MachoSect {
char* name; char* name;
char* segname;
uint64 addr; uint64 addr;
uint64 size; uint64 size;
uint32 off; uint32 off;
...@@ -44,17 +45,10 @@ struct MachoLoad { ...@@ -44,17 +45,10 @@ struct MachoLoad {
uint32 *data; uint32 *data;
}; };
typedef struct MachoDebug MachoDebug;
struct MachoDebug {
uint32 fileoffset;
uint32 filesize;
};
MachoHdr* getMachoHdr(void); MachoHdr* getMachoHdr(void);
MachoSeg* newMachoSeg(char*, int); MachoSeg* newMachoSeg(char*, int);
MachoSect* newMachoSect(MachoSeg*, char*); MachoSect* newMachoSect(MachoSeg*, char*, char*);
MachoLoad* newMachoLoad(uint32, uint32); MachoLoad* newMachoLoad(uint32, uint32);
MachoDebug* newMachoDebug(void);
int machowrite(void); int machowrite(void);
void machoinit(void); void machoinit(void);
......
...@@ -1030,8 +1030,8 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp) ...@@ -1030,8 +1030,8 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp)
uvlong textsize, datasize, bsssize; uvlong textsize, datasize, bsssize;
uchar *cmdbuf; uchar *cmdbuf;
uchar *cmdp; uchar *cmdp;
int i, hdrsize; int i, j, hdrsize;
uint32 textva, textoff, datava, dataoff; uint32 textva, textoff, datava, dataoff, symoff, symsize, pclnoff, pclnsize;
mp = &hp->e.machhdr; mp = &hp->e.machhdr;
if (leswal(mp->filetype) != MACH_EXECUTABLE_TYPE) { if (leswal(mp->filetype) != MACH_EXECUTABLE_TYPE) {
...@@ -1141,33 +1141,24 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp) ...@@ -1141,33 +1141,24 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp)
if (strcmp(seg32->segname, "__TEXT") == 0) { if (strcmp(seg32->segname, "__TEXT") == 0) {
textva = seg32->vmaddr; textva = seg32->vmaddr;
textoff = seg32->fileoff; textoff = seg32->fileoff;
textsize = seg32->vmsize;
sect32 = (MachSect32*)(cmdp + sizeof(MachSeg32)); sect32 = (MachSect32*)(cmdp + sizeof(MachSeg32));
if (strcmp(sect32->sectname, "__text") == 0) { for(j = 0; j < seg32->nsects; j++, sect32++) {
textsize = swal(sect32->size); if (strcmp(sect32->sectname, "__gosymtab") == 0) {
} else { symoff = swal(sect32->offset);
werrstr("no text section"); symsize = swal(sect32->size);
goto bad; }
if (strcmp(sect32->sectname, "__gopclntab") == 0) {
pclnoff = swal(sect32->offset);
pclnsize = swal(sect32->size);
}
} }
} }
if (strcmp(seg32->segname, "__DATA") == 0) { if (strcmp(seg32->segname, "__DATA") == 0) {
datava = seg32->vmaddr; datava = seg32->vmaddr;
dataoff = seg32->fileoff; dataoff = seg32->fileoff;
sect32 = (MachSect32*)(cmdp + sizeof(MachSeg32)); datasize = seg32->filesize;
if (strcmp(sect32->sectname, "__data") == 0) { bsssize = seg32->vmsize - seg32->filesize;
datasize = swal(sect32->size);
} else {
werrstr("no data section");
goto bad;
}
sect32++;
if (strcmp(sect32->sectname, "__nl_symbol_ptr") == 0)
sect32++;
if (strcmp(sect32->sectname, "__bss") == 0) {
bsssize = swal(sect32->size);
} else {
werrstr("no bss section");
goto bad;
}
} }
break; break;
...@@ -1188,42 +1179,39 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp) ...@@ -1188,42 +1179,39 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp)
if (strcmp(seg->segname, "__TEXT") == 0) { if (strcmp(seg->segname, "__TEXT") == 0) {
textva = seg->vmaddr; textva = seg->vmaddr;
textoff = seg->fileoff; textoff = seg->fileoff;
textsize = seg->vmsize;
sect = (MachSect64*)(cmdp + sizeof(MachSeg64)); sect = (MachSect64*)(cmdp + sizeof(MachSeg64));
if (strcmp(sect->sectname, "__text") == 0) { for(j = 0; j < seg->nsects; j++, sect++) {
textsize = swav(sect->size); print("%s %#x %#x\n", sect->sectname, swal(sect->offset), swal(sect->size));
} else { if (strcmp(sect->sectname, "__gosymtab") == 0) {
werrstr("no text section"); symoff = swal(sect->offset);
goto bad; symsize = swal(sect->size);
}
if (strcmp(sect->sectname, "__gopclntab") == 0) {
pclnoff = swal(sect->offset);
pclnsize = swal(sect->size);
}
} }
} }
if (strcmp(seg->segname, "__DATA") == 0) { if (strcmp(seg->segname, "__DATA") == 0) {
datava = seg->vmaddr; datava = seg->vmaddr;
dataoff = seg->fileoff; dataoff = seg->fileoff;
sect = (MachSect64*)(cmdp + sizeof(MachSeg64)); datasize = seg->filesize;
if (strcmp(sect->sectname, "__data") == 0) { bsssize = seg->vmsize - seg->filesize;
datasize = swav(sect->size);
} else {
werrstr("no data section");
goto bad;
}
sect++;
if (strcmp(sect->sectname, "__nl_symbol_ptr") == 0)
sect++;
if (strcmp(sect->sectname, "__bss") == 0) {
bsssize = swav(sect->size);
} else {
werrstr("no bss section");
goto bad;
}
} }
break; break;
case MACH_UNIXTHREAD: case MACH_UNIXTHREAD:
break; break;
case MACH_SYMSEG: case MACH_SYMSEG:
if (symtab == 0) if (symtab == 0) {
symtab = (MachSymSeg*)c; symtab = (MachSymSeg*)c;
else if (pclntab == 0) symoff = swal(symtab->fileoff);
symsize = swal(symtab->filesize);
} else if (pclntab == 0) {
pclntab = (MachSymSeg*)c; pclntab = (MachSymSeg*)c;
pclnoff = swal(pclntab->fileoff);
pclnsize = swal(pclntab->filesize);
}
break; break;
} }
cmdp += c->size; cmdp += c->size;
...@@ -1236,8 +1224,8 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp) ...@@ -1236,8 +1224,8 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp)
/* compute entry by taking address after header - weird - BUG? */ /* compute entry by taking address after header - weird - BUG? */
settext(fp, textva+sizeof(Machhdr) + mp->sizeofcmds, textva, textsize, textoff); settext(fp, textva+sizeof(Machhdr) + mp->sizeofcmds, textva, textsize, textoff);
setdata(fp, datava, datasize, dataoff, bsssize); setdata(fp, datava, datasize, dataoff, bsssize);
if(symtab != 0) if(symoff > 0)
setsym(fp, symtab->fileoff, symtab->filesize, 0, 0, 0, pclntab? pclntab->filesize : 0); setsym(fp, symoff, symsize, 0, 0, pclnoff, pclnsize);
free(cmd); free(cmd);
free(cmdbuf); free(cmdbuf);
return 1; return 1;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment