#include "uld.h"
#pragma hdrstop

#include "obj.h"
#include "version.h"

#define LTIMING(x) // RTIMING(x)
#define LDUMP(x)   // RDUMP(x)

static const byte std_stub[] =
{
	0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
	0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68,
	0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f,
	0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20,
	0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

String LinkJob::Symbol::ToString() const
{
	return NFormat("OBJ %5>d SEC %5>d VAL %8>d TYP %04x", obj_sec.object, obj_sec.section, value, type);
}

bool LinkJob::GlobalAddressOrder::operator () (int a, int b) const
{
	const Symbol& ga = linkjob.globals[a], & gb = linkjob.globals[b];
	if(ga.obj_sec.section < 0 || gb.obj_sec.section < 0) {
		if(ga.obj_sec.section != gb.obj_sec.section)
			return ga.obj_sec.section < gb.obj_sec.section;
		return ga.value < gb.value;
	}
	if(ga.obj_sec.object < 0 || gb.obj_sec.object < 0) {
		if(ga.obj_sec.object != gb.obj_sec.object)
			return ga.obj_sec.object < gb.obj_sec.object;
		return ga.value < gb.value;
	}
	int seca = linkjob.GetSection(ga.obj_sec).sec_map_index;
	int secb = linkjob.GetSection(gb.obj_sec).sec_map_index;
	if(seca < 0 || secb < 0)
		return seca > secb;
	int rva = linkjob.section_map[seca].rva - linkjob.section_map[secb].rva;
	if(rva) return rva < 0;
	if(ga.value != gb.value)
		return ga.value < gb.value;
	return langinfo(linkjob[linkjob.globals.GetKey(a)], linkjob[linkjob.globals.GetKey(b)]);
}

/*
unsigned LinkJob::Stab::GetHashValue() const
{
	CombineHash ch;
	ch.Put(::GetHashValue(text));
	ch.Put(MAKELONG(MAKEWORD(type, other), desc));
	ch.Put(value);
	ch.Put(fixup.object);
	ch.Put(fixup.section);
	ch.Put(fileatom);
	return ch;
}
*/

/*
bool LinkJob::Stab::Equals(const Stab& b) const
{
	return type == b.type && other == b.other
		&& desc == b.desc && value == b.value
		&& fixup == b.fixup && text == b.text
		&& fileatom == b.fileatom;
}
*/

/*
void LinkJob::Cache::Serialize(Stream& stream)
{
	stream % library % file % time % offset % size;
}
*/

LinkJob::LinkJob()
{
	start_time = msecs();

//	mangling_style = MANGLING_MSC;
	file_align = 0x1000;
	image_align = 0x1000;
	image_base = 0;
	image_fixed = false;
	stack_reserve = 0x100000;
	stack_commit = 0x1000;
	heap_reserve = 0x100000;
	heap_commit = 0x1000;
	ignore_code_alignment = true;
	linkermode = (!CompareNoCase(GetFileTitle(GetExeFilePath()), "link") ? MODE_MSLINK : MODE_GNULD);
#ifdef _DEBUG
	dump_flags = DUMP_COMMANDLINE; //DUMP_STAT | DUMP_TIMING;
#else
	dump_flags = 0;
#endif
#if defined(CPU_IA32)
	machine = COFF_IMAGE_FILE_MACHINE_I386;
#else
	machine = COFF_IMAGE_FILE_MACHINE_UNKNOWN;
#endif
	write_mapfile = false;
	mapfile_stdout = false;
	write_xref = false;
	debug_info_raw = -1;
	debug_info = false;
	show_logo = true;
	autocollect_crt_only = false; // VC71 fpinit!!!
//	cache_objects = false;
	cache_object_data = false;
	static_libraries = true;
	make_lib = false;
	make_def = false;
	make_dll = false;
	verbose = false;
	major_version = 1;
	minor_version = 1;
	major_subsystem_version = 4;
	minor_subsystem_version = 0;
	auto_dll_base = false;
	subsystem = COFF_IMAGE_SUBSYSTEM_DEFAULT;
	atoms.Add(Null);
	text_atom = Atomize(".text");
	data_atom = Atomize(".data");
	edata_atom = Atomize(".edata");
	crt_atom = Atomize(".CRT");
	ctors_atom = Atomize(".ctors");
	dtors_atom = Atomize(".dtors");
	gcc_except_atom = Atomize(".gcc_except_table");
	CTOR_LIST_atom = Atomize("__CTOR_LIST__");
	_CTOR_LIST_atom = Atomize("___CTOR_LIST__");
	CTOR_LIST_END_atom = Atomize("__CTOR_LIST_END__");
	DTOR_LIST_atom = Atomize("__DTOR_LIST__");
	_DTOR_LIST_atom = Atomize("___DTOR_LIST__");
	DTOR_LIST_END_atom = Atomize("__DTOR_LIST_END__");
	idata_atom = atoms.FindAdd(".idata");
	idata_idesc_atom = atoms.FindAdd(".idata$2");
	idata_inull_atom = atoms.FindAdd(".idata$3");
	idata_iat1_atom = atoms.FindAdd(".idata$4");
	idata_iat2_atom = atoms.FindAdd(".idata$5");
	idata_names_atom = atoms.FindAdd(".idata$6");
	rdata_atom = atoms.FindAdd(".rdata");
	bss_atom = atoms.FindAdd(".bss");
	debug_atom = atoms.FindAdd(".debug");
	drectve_atom = Atomize(".drectve");
	stab_atom = Atomize(".stab");
	stabstr_atom = Atomize(".stabstr");
	rsrc_atom = Atomize(".rsrc");
	AddGlobal(Atomize("__image_base__"), Symbol(Null, 0, COFF_IMAGE_SYM_TYPE_BASE));

	stab_section = -1;
	stabstr_section = -1;
	symbol_section = -1;
	reloc_section = -1;
	has_stabs = false;
//	has_symbols = false;
//	has_relocs = false;
	idata_last = -1;
	idata_null_found = false;

	mapping_cache_limit = 100;

	{
		ASSERT(objects.GetCount() == OBJ_MARKER);
		ObjectFile& of = objects.Add(new ObjectFile(*this, OBJ_MARKER, Null, Null, ObjectFile::INTERNAL, 0, Null, 0));
		of.sections.SetCount(OBJM_COUNT);
		static const char *sec_names[] = { "..data", ".data$PSR", ".data$PSR", "..bss", ".~bss" };
		for(int i = 1; i < OBJM_COUNT; i++) {
			ObjectFile::Section& sec = of.sections[i];
			sec.name_atom = Atomize(sec_names[i - 1]);
			sec.sec_atom = (i < OBJM_BSS_BEGIN ? data_atom : bss_atom);
			sec.size = sec.raw_size = 0;
			sec.flags = (i < OBJM_BSS_BEGIN ? COFF_IMAGE_SCN_CNT_INITIALIZED_DATA : COFF_IMAGE_SCN_CNT_UNINITIALIZED_DATA)
				| (COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE | COFF_IMAGE_SCN_ALIGN_1BYTES);
			sec.type = ObjectFile::SEC_RAW;
			sec.autocollect = true;
		}
		AddGlobal(Atomize("__data_start__"), Symbol(ObjSec(OBJ_MARKER, OBJM_DATA_BEGIN)));
		AddGlobal(Atomize("__data_end__"), Symbol(ObjSec(OBJ_MARKER, OBJM_BSS_BEGIN)));
		AddGlobal(Atomize("___RUNTIME_PSEUDO_RELOC_LIST__"), Symbol(ObjSec(OBJ_MARKER, OBJM_PSEUDO_RELOC_BEGIN)));
		AddGlobal(Atomize("___RUNTIME_PSEUDO_RELOC_LIST_END__"), Symbol(ObjSec(OBJ_MARKER, OBJM_PSEUDO_RELOC_END)));
		AddGlobal(Atomize("__bss_start__"), Symbol(ObjSec(OBJ_MARKER, OBJM_BSS_BEGIN)));
		AddGlobal(Atomize("__bss_end__"), Symbol(ObjSec(OBJ_MARKER, OBJM_BSS_END)));
	}

	{
		ASSERT(objects.GetCount() == OBJ_CTOR);
		ObjectFile& of = objects.Add(new ObjectFile(*this, OBJ_CTOR, Null, Null, ObjectFile::INTERNAL, 0, Null, 0));
		of.sections.SetCount(OBJC_COUNT);
		static const char *sec_names[] = { ".ctors", ".ctors$_end", ".dtors", ".dtors$_end", ".idata$6", ".idata$3" };
		for(int i = 1; i < OBJC_COUNT; i++) {
			ObjectFile::Section& sec = of.sections[i];
			sec.name_atom = Atomize(sec_names[i - 1]);
			sec.sec_atom = (i >= OBJC_IDATA_HOLE ? idata_atom : data_atom);
			sec.size = sec.raw_size = (i == OBJC_IDATA_NULL ? sizeof(COFF_IMAGE_IMPORT_DESCRIPTOR) : 4);
			sec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA
				| COFF_IMAGE_SCN_MEM_READ | (i == OBJC_IDATA_NULL ? 0 : COFF_IMAGE_SCN_MEM_WRITE)
				| COFF_IMAGE_SCN_ALIGN_4BYTES;
			sec.section_data.SetCount(sec.size, i == OBJC_CTOR_BEGIN || i == OBJC_DTOR_BEGIN ? 0xFF : 0x00);
			sec.type = ObjectFile::SEC_RAW;
			sec.autocollect = true;
		}
	}

/*
	if(idata_last >= 0 && !idata_null_found) {
		ObjectFile& of = objects[idata_last];
		ObjectFile::Section& ofsec = of.sections.Add();
//		ObjectFile::Section& prev = of.sections[of.sections.GetCount() - 2];
		ofsec.name_atom = idata_inull_atom;
		ofsec.sec_atom = idata_atom;
		ofsec.size = sizeof(COFF_IMAGE_IMPORT_DESCRIPTOR);
		ofsec.raw_size = 0;
		ofsec.offset = 0;
		ofsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA
			| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;
		ofsec.ref_sec_index = of.ref_sections.GetCount();
		ofsec.ref_ext_index = of.ref_externals.GetCount();
		ofsec.sec_map_index = -1;
		of.used_any = ofsec.used = true;
		ofsec.type = ObjectFile::SEC_INULL;
	}
*/

	AddGlobal(CTOR_LIST_atom,     Symbol(ObjSec(OBJ_CTOR, OBJC_CTOR_BEGIN), 0, COFF_IMAGE_SYM_TYPE_CTOR_DTOR));
	AddGlobal(_CTOR_LIST_atom,    Symbol(ObjSec(OBJ_CTOR, OBJC_CTOR_BEGIN), 0, COFF_IMAGE_SYM_TYPE_CTOR_DTOR));
	AddGlobal(CTOR_LIST_END_atom, Symbol(ObjSec(OBJ_CTOR, OBJC_CTOR_END),   0, COFF_IMAGE_SYM_TYPE_CTOR_DTOR));
	AddGlobal(DTOR_LIST_atom,     Symbol(ObjSec(OBJ_CTOR, OBJC_DTOR_BEGIN), 0, COFF_IMAGE_SYM_TYPE_CTOR_DTOR));
	AddGlobal(_DTOR_LIST_atom,    Symbol(ObjSec(OBJ_CTOR, OBJC_DTOR_BEGIN), 0, COFF_IMAGE_SYM_TYPE_CTOR_DTOR));
	AddGlobal(DTOR_LIST_END_atom, Symbol(ObjSec(OBJ_CTOR, OBJC_DTOR_END),   0, COFF_IMAGE_SYM_TYPE_CTOR_DTOR));
 {
		ASSERT(objects.GetCount() == OBJ_EXPORT);
		ObjectFile& of = objects.Add(new ObjectFile(*this, OBJ_EXPORT, Null, Null, ObjectFile::INTERNAL, 0, Null, 0));
		of.sections.SetCount(OBJE_COUNT);
		static const char *sec_names[] = { ".edata$1dir", ".edata$2adr", ".edata$3ptr", ".edata$4ord", ".edata$5nam" };
		for(int i = 1; i < OBJE_COUNT; i++) {
			ObjectFile::Section& sec = of.sections[i];
			sec.name_atom = Atomize(sec_names[i - 1]);
			sec.sec_atom = edata_atom;
			sec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA
				| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE | COFF_IMAGE_SCN_ALIGN_4BYTES;
			sec.type = ObjectFile::SEC_RAW;
			sec.autocollect = true;
		}
	}

	output_stab_info.SetCountR(STAB_STABSIZE, 0);
	output_stab_strings.SetCountR(1, 0);
}

String LinkJob::DemangleAtom(int atom) const
{
	return ::DemangleName(atoms[atom], linkermode == MODE_MSLINK ? MANGLING_MSC : MANGLING_GCC);
}

FileMapping& LinkJob::GetMapping(String filename)
{
	int f = mapping_cache.Find(filename);
	if(f >= 0)
		return mapping_cache[f].mapping;
	if(mapping_cache.GetCount() >= mapping_cache_limit) {
		for(int t = 0; t < mapping_cache.GetCount() && mapping_cache.GetCount() >= mapping_cache_limit; t++)
			if(mapping_cache[t].lock == 0)
				mapping_cache.Remove(t--);
	}
	One<Cache> nc = new Cache;
	nc->lock = 0;
	static FileMapping dummy;
	if(!nc->mapping.Open(filename))
		return dummy;
	Cache *pc = ~nc;
	mapping_cache.Add(filename, -nc);
	return pc->mapping;
}

void LinkJob::LockMapping(String filename)
{
	int f = mapping_cache.Find(filename);
	if(f >= 0)
		mapping_cache[f].lock++;
}

void LinkJob::UnlockMapping(String filename)
{
	int f = mapping_cache.Find(filename);
	if(f >= 0)
		mapping_cache[f].lock--;
}

void LinkJob::ReadCommand(const char *cmd, bool user)
{
	ReadCommand(SplitCmdArgs(cmd), user);
}

String LinkJob::Usage()
{
	return String().Cat()
		<< NFormat("uld COFF linker, version %d.%d.r%d, release date: %`\n%s",
			ULD_VERSION_MAJOR, ULD_VERSION_MINOR, ULD_VERSION_RELEASE, ULD_DATE, ULD_COPYRIGHT) << "\n"
		"\n"
		"Currently supported MS linker-compatible options:\n"
		"\n"
		"-align:<alignment factor:1,2,4,8,16,...65536>\n"
		"-base:<image base address>\n"
		"-debug\n"
		"-defaultlib:<default library path>\n"
		"-fixed .. build fixed address executable\n"
		"-lib .. build static library\n"
		"-libpath:<library path>\n"
		"-machine:<one-of " << COFFMachineNames() << ">\n"
		"-map[:<map file>]\n"
		"-merge:<section>=<section>\n"
		"-nodefaultlib:<library to exclude from defaults>\n"
		"-disallowlib:<same as -nodefaultlib>\n"
		"-entry:<symbol> .. override default entry point\n"
		"-nologo .. suppress startup banner\n"
		"-out:<filename> .. name of exe file to produce\n"
		"-stub:<filename> .. file to use as DOS stub\n"
		"-subsystem:<one-of: NATIVE, CONSOLE, POSIX, WINDOWS,"
		" WINDOWSCE, OS2>\n"
		"-version:<major>[.<minor>] .. DLL version number\n"
		"-verbose .. display additional link information\n"
		"\n"
		"Currently supported GNU LD-compatible options:\n"
		"\n"
		"-Map <mapfile>"
		"--subsystem <one-of: windows, console>\n"
		"--map-file .. outputs on stdout\n"
		"--start-group, --end-group (ignored, automatic)\n"
		"--gc-sections (ignored, automatic)\n"
		"--major-image-version <major> .. major DLL version\n"
		"--minor-image-version <minor> .. minor DLL version\n"
		"-B <one-of: static, dynamic>\n"
		"-e <symbol> .. override default entry point\n"
		"-L <library path>\n"
		"-l <file to load as lib<file>.a>\n"
		"-M .. write mapfile on stdout\n"
		"-O ### .. optimization level (ignored)\n"
		"-o ### .. write output on file ###\n"
		"-s .. strip debug information from file\n"
		"\n"
		"uld-specific options:\n"
//		"-nocache .. do not cache link information in helper file\n"
//		"   (default = on)\n"
		"-collectdata .. automatically collect all initialized data\n"
		"   from any referenced object (default=CRT sections only)\n"
		"-aligncode .. honor alignment in code sections (default=off)\n"
		"-keepsections .. keep /xxx gcc code COMDAT section names\n"
		"   (default = convert to .text / .data / .bss)\n"
		"-dump:[stab;][global;]<exe file name> .. dump executable\n"
		"-dumpcollect .. dump object tree collector output\n"
		"-dumpcomdats .. dump remapped COMDATs\n"
		"-dumpcommand .. dump command line as C string constant\n"
		"-dumpdll .. dump exports from loaded dynamic libraries\n"
		"-dumpexports .. dump export systems with ordinals\n"
		"-dumpfixups .. dump fixups\n"
		"-dumpimp .. dump import libraries\n"
		"-dumpimplib .. dump contents of loaded import libraries\n"
		"-dumpobj .. dump object files\n"
		"-dumpobjlib .. dump contents of loaded object libraries\n"
		"-dumpsec[:all | :defines] .. dump sections\n"
		"     / all contributions / all contrib's + defined symbols\n"
		"-dumpstabs .. dump output stabs\n"
		"-dumpstabtypes .. dump stab collector output\n"
		"-dumpstat .. dump general linking statistics\n"
		"-dumptime .. dump linker phase timing\n"
		"-makedef .. write DEF-file based on given import libraries\n"
		"-mapall .. write all symbols to map file\n"
		"-mapout .. write mapfile to <outputfile>.map\n"
		"-mapxref .. write cross references into map file\n"
		"-mapunused .. write unused linked in symbols to map file\n"
	;
}

void LinkJob::ReadCommand(const Vector<String>& cmdlist, bool user)
{
	command_args.Append(cmdlist);
	if(user)
		user_command_args.Append(cmdlist);
	int start = msecs();
	for(int i = 0; i < cmdlist.GetCount(); i++) {
		String arg = cmdlist[i];
		bool is_opt = false;
		if(*arg == '/') {
			const char *p = arg;
			is_opt = true;
			while(*++p && *p != ':' && *p != '=' && *p != '/' && *p != '.')
				;
			is_opt = (*p == 0 || *p == ':');
		}

		if(is_opt || *arg == '-') {
			const char *ptr = ~arg + 1;
			const char *end = ptr;
			while(*end && *end != ':')
				end++;
			String cmd(ptr, end);
			String lcmd = ToLower(cmd);
			if(cmd == "Map") {
				linkermode = MODE_GNULD;
				write_mapfile = true;
				mapfile = FetchCmdArg(cmdlist, i);
				continue;
			}
			String val;
			if(*end == ':')
				val = end + 1;
			bool eaten = true;
			if(lcmd == "align") {
				int at = IsDigit(*val) ? ScanInt(val) : 4096;
				image_align = 0x100;
				while(image_align < at && image_align < 65536)
					image_align <<= 1;
				if(image_align < 0x1000)
					file_align = image_align;
			}
			else if(lcmd == "alternatename") {
				int f = val.Find('=');
				if(f >= 0) {
					String name = val.Left(f), alt = val.Mid(f + 1);
					AddWeakExternal(Atomize(name), Atomize(alt));
				}
				else
					PutStdOut("'alternatename' option has invalid syntax (missing '=')");
			}
			else if(lcmd == "base")
				image_base = ScanCInt(val);
			else if(lcmd == "debug")
				debug_info_raw = 1;
			else if(lcmd == "defaultlib" && !IsNull(val))
				defaultlibs.FindAdd(NormalizePathCase(val));
			else if(lcmd == "entry" && !IsNull(val))
				entrypoint = val;
			else if(lcmd == "fixed")
				image_fixed = true;
			else if(lcmd == "include") {
			}
			else if(lcmd == "lib")
				make_lib = true;
			else if(lcmd == "libpath" && !IsNull(val))
				libpaths.Add(val);
			else if(lcmd == "machine" && !IsNull(val)) {
				val = ToUpper(val);
				machine = COFFMachineCode(ToUpper(val));
				if(machine == COFF_IMAGE_FILE_MACHINE_UNKNOWN)
					PutStdOut(NFormat("Unknown machine type: %s; supported machines are: %s", val, COFFMachineNames()));
			}
			else if(lcmd == "map") {
				write_mapfile = true;
				if(!IsNull(val))
					mapfile = val;
			}
			else if(lcmd == "merge") {
				const char *p = val;
				while(*p && *p != '=')
					p++;
				String from(~val, p);
				if(*p++ != '=' || !*p || IsNull(from))
					PutStdOut("Invalid syntax of /merge command.");
				else
					section_merge.GetAdd(Atomize(from)) = Atomize(p);
			}
			else if((lcmd == "nodefaultlib" || lcmd == "disallowlib") && !IsNull(val))
				nodefaultlibs.FindAdd(NormalizePathCase(val));
			else if(lcmd == "nologo")
				show_logo = false;
			else if(lcmd == "out") {
				if(!IsNull(outputfile))
					PutStdOut(NFormat("Duplicate output file: %s (was %s)", val, outputfile));
				outputfile = val;
			}
			else if(lcmd == "stub") {
				if(!IsNull(stub_filename))
					PutStdOut(NFormat("Duplicate stub file name: %s (was %s)", val, stub_filename));
				stub_filename = val;
			}
			else if(lcmd == "subsystem" && !IsNull(val)) {
				val = ToUpper(val);
				/**/ if(val == "CONSOLE")   subsystem = COFF_IMAGE_SUBSYSTEM_WINDOWS_CUI;
				else if(val == "NATIVE")    subsystem = COFF_IMAGE_SUBSYSTEM_NATIVE;
				else if(val == "POSIX")     subsystem = COFF_IMAGE_SUBSYSTEM_POSIX_CUI;
				else if(val == "WINDOWS")   subsystem = COFF_IMAGE_SUBSYSTEM_WINDOWS_GUI;
#ifdef COMPILER_MSC
				else if(val == "WINDOWSCE") subsystem = COFF_IMAGE_SUBSYSTEM_WINDOWS_CE_GUI;
#endif
				else if(val == "OS2")       subsystem = COFF_IMAGE_SUBSYSTEM_OS2_CUI;
				else
					PutStdOut(NFormat("Unknown subsystem: %s", val));
			}
			else if(lcmd == "version" && !IsNull(val)) {
				const char *p = val;
				major_version = (word)ScanInt(p, &p);
				if(*p++ == '.' && IsDigit(*p))
					minor_version = (word)ScanInt(p);
			}
			else
				eaten = false;
			if(eaten)
				linkermode = MODE_MSLINK;
			else {
				eaten = true;
				if(lcmd == "aligncode")
					ignore_code_alignment = false;
				else if(lcmd == "collectdata")
					autocollect_crt_only = false;
				else if(lcmd == "makedef")
					make_def = true;
				else if(lcmd == "dump" && !IsNull(val)) {
					void DumpFile(String fn);
					DumpFile(val);
				}
				else if(lcmd == "dumpcollect")
					dump_flags |= DUMP_COLLECTOR;
				else if(lcmd == "dumpcomdats")
					dump_flags |= DUMP_COMDAT_REMAPS;
				else if(lcmd == "dumpcommand")
					dump_flags |= DUMP_COMMANDLINE;
				else if(lcmd == "dumpdll")
					dump_flags |= DUMP_DLL_EXPORTS;
				else if(lcmd == "dumpexports")
					dump_flags |= DUMP_EXPORTS;
				else if(lcmd == "dumpfixups")
					dump_flags |= DUMP_REF_FIXUPS;
				else if(lcmd == "dumpimp")
					dump_flags |= DUMP_IMPORT;
				else if(lcmd == "dumpimplib")
					dump_flags |= DUMP_IMPORT_LIBS;
				else if(lcmd == "dumpobj")
					dump_flags |= DUMP_OBJECTS;
				else if(lcmd == "dumpobjlib")
					dump_flags |= DUMP_OBJECT_LIBS;
				else if(lcmd == "dumpsec")
					dump_flags |= (val == "def" ? DUMP_SEC_DEFINES : val == "all" ? DUMP_SECTIONS_ALL : DUMP_SECTIONS);
				else if(lcmd == "dumpstabs")
					dump_flags |= DUMP_STABS;
				else if(lcmd == "dumpstabtypes")
					dump_flags |= DUMP_STAB_TYPES;
				else if(lcmd == "dumpstat")
					dump_flags |= DUMP_STAT;
				else if(lcmd == "dumptime")
					dump_flags |= DUMP_TIMING;
				else if(lcmd == "mapall")
					dump_flags |= DUMP_MAP_ALL;
				else if(lcmd == "mapout")
					write_mapfile = true;
				else if(lcmd == "mapxref")
					write_xref = true;
				else if(lcmd == "mapunused")
					dump_flags |= DUMP_MAP_UNUSED;
				else if(lcmd == "verbose")
					verbose = true;
				else
					eaten = false;
			}
			if(!eaten) {
				ptr = ~arg + 1;
				const char *pfx;
				eaten = true;
				switch(*ptr++) {
				case '-':
					if(pfx = EatPrefix(ptr, "dll-search-prefix="))
						dll_search_prefix = pfx;
					else if(!strcmp(ptr, "dynamic"))
						static_libraries = false;
					else if(!strcmp(ptr, "enable-auto-image-base"))
						auto_dll_base = true;
					else if(!strcmp(ptr, "gc-sections"))
						; // automatic for the moment
					else if(!strcmp(ptr, "major-image-version"))
						major_version = (word)ScanInt(FetchCmdArg(cmdlist, i));
					else if(!strcmp(ptr, "major-subsystem-version"))
						major_subsystem_version = (word)ScanInt(FetchCmdArg(cmdlist, i));
					else if(!strcmp(ptr, "map-file"))
						write_mapfile = mapfile_stdout = true;
					else if(!strcmp(ptr, "minor-image-version"))
						minor_version = (word)ScanInt(FetchCmdArg(cmdlist, i));
					else if(!strcmp(ptr, "minor-subsystem-version"))
						minor_subsystem_version = (word)ScanInt(FetchCmdArg(cmdlist, i));
					else if(!strcmp(ptr, "start-group") || !strcmp(ptr, "end-group"))
						; // ignore these for the moment
					else if(!strcmp(ptr, "static"))
						static_libraries = true;
					else if(!strcmp(ptr, "shared"))
						make_dll = true;
					else if(!strcmp(ptr, "subsystem")) {
						String sub = FetchCmdArg(cmdlist, i);
						if(sub == "windows") {
							if(subsystem != COFF_IMAGE_SUBSYSTEM_WINDOWS_CUI)
								subsystem = COFF_IMAGE_SUBSYSTEM_WINDOWS_GUI;
						}
						else if(sub == "windowsce") {
							subsystem = COFF_IMAGE_SUBSYSTEM_WINDOWS_CE_GUI;
						}
						else if(sub == "console")
							subsystem = COFF_IMAGE_SUBSYSTEM_WINDOWS_CUI;
						else
							PutStdOut(NFormat("Unknown --subsystem: %s", sub));
					}
					else
						eaten = false;
					break;

				case 'B':
					{
						String style = FetchCmdArg(cmdlist, i, ptr);
						if(style == "static")
							static_libraries = true;
						else if(style == "dynamic")
							static_libraries = true;
						else if(style == "shareable")
							make_dll = true;
						else
							eaten = false;
						break;
					}

				case 'e':
					entrypoint = FetchCmdArg(cmdlist, i, ptr);
					break;

				case 'L':
					libpaths.Add(FetchCmdArg(cmdlist, i, ptr));
					break;

				case 'l': {
					files_to_load.Add() << "lib" << ptr << ".a";
					break;
				}

				case 'M':
					write_mapfile = true;
					mapfile_stdout = true;
					break;

				case 'O':
					ptr = FetchCmdArg(cmdlist, i, ptr);
					PutStdOut(NFormat("(option '-O %s' ignored)", ptr));
					break;

				case 'o':
					outputfile = FetchCmdArg(cmdlist, i, ptr);
					break;

				case 's':
					debug_info_raw = 0;
					image_fixed = true;
					break;

				case 'x':
				case 'X':
					debug_info_raw = 0;
					break;

				default:
					eaten = false;
					break;
				}
				if(eaten)
					linkermode = MODE_GNULD;
			}
			if(!eaten)
				PutStdOut(NFormat("Unknown option: %s", arg));
		}
		else
			files_to_load.Add(arg);
	}
}

String FindLinkFile(String fn, String dir, bool has_ext)
{
	String natdir = NormalizePathCase(NativePath(dir));
	String nat = NormalizePathCase(NativePath(fn));
	String out;
	if(FileExists(out = AppendFileName(natdir, nat)))
		return out;
	if(has_ext)
		return Null;
	if(FileExists(out = AppendFileName(natdir, nat + ".obj"))
	|| FileExists(out = AppendFileName(natdir, nat + ".o"))
	|| FileExists(out = AppendFileName(natdir, nat + ".lib"))
	|| FileExists(out = AppendFileName(natdir, nat + ".a"))
	|| FileExists(out = AppendFileName(natdir, "lib" + nat + ".a")))
		return out;
	return Null;
}

void LinkJob::LoadFile(String fn, bool defaultlib)
{
	LTIMING("LinkJob::LoadFile");
	String relfn = NormalizeRelPath(NormalizePathCase(fn));
	if(loaded_files.Find(relfn) >= 0)
		return;
	bool has_ext = *GetFileExtPos(relfn);
	String file;
	ObjectFile::FILETYPE ft = ObjectFile::FULL_PATH;
	file = FindLinkFile(relfn, Null, has_ext);
	if(IsNull(file) && !IsNull(dll_search_prefix))
		file = FindLinkFile(AppendFileName(GetFileDirectory(relfn), dll_search_prefix + GetFileName(relfn)), Null, has_ext);
#ifdef PLATFORM_WIN32
	String cygfn = TranslateCygWinPath(fn);
	if(IsNull(file) && !IsNull(cygfn))
		file = FindLinkFile(cygfn, Null, has_ext);
#endif
	if(IsNull(file) && !IsFullPath(relfn)) {
		ft = ObjectFile::LIB_PATH;
		for(int i = 0; i < libpaths.GetCount(); i++) {
			if(!IsNull(file = FindLinkFile(relfn, libpaths[i], has_ext)))
				break;
#ifdef PLATFORM_WIN32
			if(!IsNull(cygfn = TranslateCygWinPath(libpaths[i])) && !IsNull(file = FindLinkFile(relfn, cygfn, has_ext)))
				break;
#endif
		}
	}
	if(IsNull(outputfile))
		outputfile = ForceExt(file, "");
//	if(cache_objects && IsNull(cache_file_name))
//		cache_file_name = ForceExt(outputfile, ".uld");
	if(defaultlib)
		ft = ObjectFile::DEFAULTLIB;
	FileMapping& mapping = LinkJob::GetMapping(file);
	if(!mapping.IsOpen()) {
		PutStdOut(NFormat("File '%s' not found.", fn));
		return;
	}
	if(loaded_files.Find(file) >= 0)
		return;
	loaded_files.Add(fn);
//	loaded_files.Add(full);
	loaded_files.Add(file);
	if(dump_flags & DUMP_OBJECTS)
		PutStdOut(file);
	if(mapping[0] == 'M' && mapping[1] == 'Z') {
		LoadDLL(file, mapping, ft);
		return;
	}
	if(*(const word *)~mapping == machine) {
		if(ft == ObjectFile::FULL_PATH)
			ft = ObjectFile::DIRECT;
		LoadObject(file, mapping, ft);
		return;
	}
	if(!memcmp(~mapping, "!<arch>\n", 8)) {
		LoadLibrary(file, mapping, ft);
		return;
	}
	PutStdOut(NFormat("File '%s' has unknown type.", file));
}

void LinkJob::LoadObject(String file, const FileMapping& mapping, ObjectFile::FILETYPE filetype)
{
	LTIMING("LinkJob::LoadObject");
	if(verbose)
		PutStdOut(NFormat("%s: loading object file", file));
	int nobj = objects.GetCount();
	ObjectFile& obj = objects.Add(new ObjectFile(*this, nobj, Null, file, filetype, 0, mapping.GetTime(), (unsigned)mapping.GetFileSize()));
	if(cache_object_data) {
		obj.object_data = String(~mapping, obj.object_size);
		obj.ReadFile(obj.object_data);
	}
	else
		obj.ReadFile(~mapping);
}

void LinkJob::LoadLibrary(String file, const FileMapping& mapping, ObjectFile::FILETYPE filetype)
{
	if(verbose)
		PutStdOut(NFormat("%s: loading static library", file));
	LTIMING("LinkJob::LoadLibrary");
	int objcount = -1;
	String libprefix = GetFileTitle(file) + ":";
	const byte *ptr = mapping.GetIter(8);
	const byte *end = mapping.End();
	const byte *longptr = NULL;
//	int objid = 0;
	Time file_time = mapping.GetTime();
	enum { HEADER_SIZE = sizeof(COFF_IMAGE_ARCHIVE_MEMBER_HEADER) };
	while(ptr + HEADER_SIZE <= end && ptr[HEADER_SIZE - 2] == COFF_IMAGE_ARCHIVE_END1
		&& ptr[HEADER_SIZE - 1] == COFF_IMAGE_ARCHIVE_END2) {
//		if(*ptr == '\n')
//			ptr++;
		int offset = ptr - mapping.Begin();
		COFF_IMAGE_ARCHIVE_MEMBER_HEADER hdr;
		memcpy(&hdr, ptr, sizeof(hdr));
		ptr += sizeof(hdr);
		int size = atoi((const char *)hdr.Size);
		const byte *brk = ptr + ((size + 1) & -2);
		if(hdr.Name[0] == '/' && hdr.Name[1] == '/') { // longnames
			longptr = ptr;
		}
		else if(hdr.Name[0] != '/' || hdr.Name[1] != ' ') { // object
			const byte *nameptr, *namelim;
			if(hdr.Name[0] != '/') {
				nameptr = hdr.Name;
				namelim = nameptr + __countof(hdr.Name);
			}
			else {
				int loff = atoi((const char *)hdr.Name + 1);
				nameptr = longptr + loff;
				namelim = end;
			}
			const byte *p = nameptr;
			while(p < namelim && *p && *p != '/')
				p++;
			String objname(nameptr, p - nameptr);
			String modname = libprefix + objname;
			const COFF_IMPORT_OBJECT_HEADER *imphdr = (const COFF_IMPORT_OBJECT_HEADER *)ptr;
			if(imphdr->Sig1 == 0 && imphdr->Sig2 == 0xFFFF) {
				if(imphdr->Machine != machine)
					throw Exc(NFormat("%s: invalid machine type (0x%04x) in import library module", file, imphdr->Machine));
				const char *pname = (const char *)ptr + sizeof(COFF_IMPORT_OBJECT_HEADER);
				atom_t impsym_atom = Atomize(pname);
				atom_t dll_atom = Atomize(NormalizePathCase(pname + strlen(pname) + 1));
				if(dump_flags & DUMP_IMPORT_LIBS) {
					String iname = atoms[impsym_atom];
					PutStdOut(NFormat(iname == pname ? "%18<s %08x  @%5>d  %s" : "%18<s %08x  @%5>d  %s -> %s",
						atoms[dll_atom], offset, imphdr->Hint, pname, atoms[impsym_atom]));
				}
				int objn = dll_objects.Get(dll_atom, -1);
				if(objn < 0) {
					dll_objects.Add(dll_atom, objn = objects.GetCount());
					ObjectFile& obj = objects.Add(new ObjectFile(*this, objn, file, objname, filetype, 0, file_time, size));
					obj.dll_atom = dll_atom;
					obj.sections.SetCount(ObjectFile::DLL_SECTIONS);
					ObjectFile::Section& stubsec = obj.sections[ObjectFile::SEC_DLL_STUBS];
					stubsec.name_atom = stubsec.sec_atom = text_atom;
					stubsec.flags = COFF_IMAGE_SCN_CNT_CODE | COFF_IMAGE_SCN_ALIGN_1BYTES
						| COFF_IMAGE_SCN_MEM_EXECUTE | COFF_IMAGE_SCN_MEM_READ;
					ObjectFile::Section& namesec = obj.sections[ObjectFile::SEC_DLL_NAMES];
					namesec.name_atom = idata_names_atom;
					namesec.sec_atom = idata_atom;
					namesec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_2BYTES | COFF_IMAGE_SCN_MEM_READ;
					ObjectFile::Section& impsec = obj.sections[ObjectFile::SEC_DLL_IMPORTS];
					impsec.name_atom = idata_iat1_atom;
					impsec.sec_atom = idata_atom;
					impsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_4BYTES
						| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;
					ObjectFile::Section& boundsec = obj.sections[ObjectFile::SEC_DLL_BOUND];
					boundsec.name_atom = idata_iat2_atom;
					boundsec.sec_atom = idata_atom;
					boundsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_4BYTES
						| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;
					ObjectFile::Section& descsec = obj.sections[ObjectFile::SEC_DLL_DESCRIPTOR];
					descsec.name_atom = idata_idesc_atom;
					descsec.sec_atom = idata_atom;
					descsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_4BYTES
						| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;
/*
					ObjectFile::Section& nullsec = obj.sections[ObjectFile::SEC_DLL_NULL_DESC];
					nullsec.name_atom = idata_inull_atom;
					nullsec.sec_atom = idata_atom;
					nullsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_4BYTES
						| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;
*/
				}
				ObjectFile::Import& dllimp = objects[objn].imports.Add();
				dllimp.app_atom = impsym_atom;
				dllimp.ordinal_hint = imphdr->Hint;
				switch(imphdr->NameType) {
				case COFF_IMPORT_OBJECT_ORDINAL:
					dllimp.ordinal_hint = imphdr->Ordinal;
					while(*pname == '_')
						pname++;
					break;

				case COFF_IMPORT_OBJECT_NAME:
					dllimp.imp_atom = impsym_atom;
					break;

				case COFF_IMPORT_OBJECT_NAME_NO_PREFIX:
				case COFF_IMPORT_OBJECT_NAME_UNDECORATE:
					while(*pname == '@' || *pname == '_' || *pname == '?')
						pname++; {
						const char *end = pname;
						if(imphdr->NameType == COFF_IMPORT_OBJECT_NAME_NO_PREFIX)
							end += strlen(end);
						else
							while(*end && *end != '@')
								end++;
						dllimp.imp_atom = Atomize(String(pname, end));
					}
					break;

				default:
					throw Exc(NFormat("%s: invalid import name type (%d)", modname, imphdr->NameType));
				}
				Symbol global;
				global.obj_sec.object = objn;
				global.value = objects[objn].imports.GetCount() - 1;
				if(imphdr->Type == COFF_IMPORT_CODE) {
					global.obj_sec.section = ObjectFile::SEC_DLL_STUBS;
					global.type = COFF_IMAGE_SYM_TYPE_IMPORT_STUB;
					AddGlobal(impsym_atom, global);
				}
				global.obj_sec.section = ObjectFile::SEC_DLL_BOUND;
				int import_atom = Atomize(String("__imp__") + pname);
				global.type = (imphdr->NameType == COFF_IMPORT_OBJECT_ORDINAL
					? COFF_IMAGE_SYM_TYPE_IMPORT_ORDINAL : COFF_IMAGE_SYM_TYPE_IMPORT_NAME);
				AddGlobal(import_atom, global);
//				RLOG("Import atom " << atoms[import_atom] << " in " << modname);
			}
			else {
				int nobj = objects.GetCount();
				if(objcount < 0)
					objcount = nobj;
				ObjectFile& obj = objects.Add(new ObjectFile(*this, nobj, file, objname,
					filetype, ptr - mapping.Begin(), file_time, size));
//				if(cache_object_data && filetype != ObjectFile::DEFAULTLIB)
//				{
//					obj.object_data = String(ptr, size);
//					obj.ReadFile(obj.object_data);
//				}
//				else
					obj.ReadFile(ptr);
				if(dump_flags & DUMP_OBJECT_LIBS) {
					for(int g = 0; g < globals.GetCount(); g++) {
						const Symbol& sym = globals[g];
						if(sym.obj_sec.object != nobj || sym.type >= COFF_IMAGE_SYM_TYPE_SPECIAL)
							continue;
						const ObjectFile& of = objects[sym.obj_sec.object];
						String secname;
						if(sym.obj_sec.section < 0)
							secname = "ABS";
						else if(sym.obj_sec.section == 0)
							secname = "COMDAT";
						else
							secname = atoms[of.sections[sym.obj_sec.section].name_atom];
						PutStdOut(NFormat("%15<s  %12<s  %12<s  %08x  %s",
							GetFileName(of.library_file), GetFileName(of.object_file), secname, sym.value, DemangleAtom(globals.GetKey(g))));
					}
				}
			}
		}
		ptr = brk;
	}
}

void LinkJob::LoadDLL(String file, const FileMapping& mapping, ObjectFile::FILETYPE filetype)
{
	try {
		if(verbose)
			PutStdOut(NFormat("%s: loading shared library", file));
		unsigned header;
		if(mapping.GetFileSize() < 0x40 || (header = PeekIL(&mapping[0x3C]))
			+ 4 + sizeof(COFF_IMAGE_FILE_HEADER) + sizeof(COFF_IMAGE_OPTIONAL_HEADER32) > (unsigned)mapping.GetFileSize()
		|| PeekIL(&mapping[header]) != 'P' + 256 * 'E')
			throw Exc("not a valid PE file");
		COFF_IMAGE_FILE_HEADER hdr;
		COFF_IMAGE_OPTIONAL_HEADER32 opthdr;
		memcpy(&hdr, &mapping[header + 4], sizeof(hdr));
		if(hdr.Machine != machine)
			throw Exc(NFormat("invalid machine code: %s", COFFMachineName(hdr.Machine)));
		if(!(hdr.Characteristics & IMAGE_FILE_DLL))
			throw Exc("not a dynamic library");
		if(hdr.SizeOfOptionalHeader < sizeof(opthdr))
			throw Exc(NFormat("invalid optional header size = %d", hdr.SizeOfOptionalHeader));
		memcpy(&opthdr, &mapping[header + 4 + sizeof(hdr)], sizeof(opthdr));
		if(opthdr.NumberOfRvaAndSizes < COFF_IMAGE_DIRECTORY_ENTRY_EXPORT)
			throw Exc("not enough data directories in image (EXPORT section not present)");
		unsigned export_rva = opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
		unsigned export_end = export_rva + opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
		Buffer<COFF_IMAGE_SECTION_HEADER> sections(hdr.NumberOfSections);
		memcpy(sections, &mapping[header + 4 + sizeof(hdr) + hdr.SizeOfOptionalHeader],
			hdr.NumberOfSections * sizeof(COFF_IMAGE_SECTION_HEADER));
		int rva_fpos = 0;
		bool found = false;
		for(int i = 0; i < hdr.NumberOfSections; i++) {
			const COFF_IMAGE_SECTION_HEADER& sec = sections[i];
			if(export_rva >= sec.VirtualAddress && export_end <= sec.VirtualAddress + sec.Misc.VirtualSize) {
				rva_fpos = sec.PointerToRawData - sec.VirtualAddress;
				found = true;
				break;
			}
		}
		if(!found)
			throw Exc("invalid export section RVA (not in any section)");
		COFF_IMAGE_EXPORT_DIRECTORY_TABLE expdir;
		memcpy(&expdir, &mapping[export_rva + rva_fpos], sizeof(expdir));

		atom_t dll_atom = Atomize(ToUpper(GetFileName(file)));
		int objn = objects.GetCount();
		dll_objects.Add(dll_atom, objn);
		ObjectFile& obj = objects.Add(new ObjectFile(*this, objn, Null, file, filetype, 0, mapping.GetTime(), (unsigned)mapping.GetFileSize()));
		obj.dll_atom = dll_atom;
		obj.sections.SetCount(ObjectFile::DLL_SECTIONS);
		ObjectFile::Section& stubsec = obj.sections[ObjectFile::SEC_DLL_STUBS];
		stubsec.name_atom = stubsec.sec_atom = text_atom;
		stubsec.flags = COFF_IMAGE_SCN_CNT_CODE
			| (machine == COFF_IMAGE_FILE_MACHINE_I386
				? COFF_IMAGE_SCN_ALIGN_1BYTES
				: COFF_IMAGE_SCN_ALIGN_4BYTES)
			| COFF_IMAGE_SCN_MEM_EXECUTE | COFF_IMAGE_SCN_MEM_READ;
		ObjectFile::Section& namesec = obj.sections[ObjectFile::SEC_DLL_NAMES];
		namesec.name_atom = idata_names_atom;
		namesec.sec_atom = idata_atom;
		namesec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_2BYTES | COFF_IMAGE_SCN_MEM_READ;
		ObjectFile::Section& impsec = obj.sections[ObjectFile::SEC_DLL_IMPORTS];
		impsec.name_atom = idata_iat1_atom;
		impsec.sec_atom = idata_atom;
		impsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_4BYTES
			| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;
		ObjectFile::Section& boundsec = obj.sections[ObjectFile::SEC_DLL_BOUND];
		boundsec.name_atom = idata_iat2_atom;
		boundsec.sec_atom = idata_atom;
		boundsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_4BYTES
			| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;
		ObjectFile::Section& descsec = obj.sections[ObjectFile::SEC_DLL_DESCRIPTOR];
		descsec.name_atom = idata_idesc_atom;
		descsec.sec_atom = idata_atom;
		descsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_ALIGN_4BYTES
			| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;

		if(dump_flags & DUMP_DLL_EXPORTS)
			PutStdOut(NFormat("%s: %d named export(s):", file, (int)expdir.NumberOfNamePointers));
		for(int i = 0; i < (int)expdir.NumberOfNamePointers; i++) {
			int nameptr = PeekIL(&mapping[expdir.NamePointerTableRVA + rva_fpos + 4 * i]);
			String name = (const char *)&mapping[nameptr + rva_fpos];
			word ordinal = PeekIW(&mapping[expdir.OrdinalTableRVA + rva_fpos + 2 * i]);
			if(ordinal >= (int)expdir.AddressTableEntries)
				throw Exc(NFormat("export symbol %s has invalid ordinal: %d (# address entries = %d)",
					name, ordinal, (int)expdir.AddressTableEntries));
			unsigned bound = PeekIL(&mapping[expdir.ExportAddressTableRVA + rva_fpos + 4 * ordinal]);

			if(dump_flags & DUMP_DLL_EXPORTS)
				PutStdOut(NFormat("\t(%d) %s", (int)(ordinal + expdir.OrdinalBase), name));
			ObjectFile::Import& dllimp = obj.imports.Add();
			dllimp.app_atom = Atomize("_" + name);
			dllimp.imp_atom = Atomize(name);
			dllimp.ordinal_hint = ordinal + expdir.OrdinalBase;
			Symbol global;
			global.obj_sec.object = objn;
			global.value = obj.imports.GetCount() - 1;
			unsigned sec_flags = 0;
			for(int s = 0; s < hdr.NumberOfSections; s++) {
				const COFF_IMAGE_SECTION_HEADER& sec = sections[s];
				if(bound >= sec.VirtualAddress && bound < sec.VirtualAddress + sec.Misc.VirtualSize) {
					sec_flags = sec.Characteristics;
					break;
				}
			}
			if(sec_flags & COFF_IMAGE_SCN_CNT_CODE) {
				global.obj_sec.section = ObjectFile::SEC_DLL_STUBS;
				global.type = COFF_IMAGE_SYM_TYPE_IMPORT_STUB;
				AddGlobal(dllimp.app_atom, global);
			}
			global.obj_sec.section = ObjectFile::SEC_DLL_BOUND;
			int import_atom = Atomize("__imp__" + name);
			global.type = COFF_IMAGE_SYM_TYPE_IMPORT_NAME;
			AddGlobal(import_atom, global);
		}
	}
	catch(Exc e) {
		PutStdOut(NFormat("%s: %s", file, e));
	}
}

void LinkJob::Dump() const
{
	RLOG("LinkJob(" << objects.GetCount() << " objects)");
	int i;
	RLOG("DefaultLibs: " << defaultlibs.GetCount());
	for(i = 0; i < defaultlibs.GetCount(); i++)
		RLOG(NFormat("defaultlib[%d] = %s, %s", i, defaultlibs[i],
			nodefaultlibs.Find(defaultlibs[i]) >= 0 ? "ignored" : "used"));
	RLOG("NoDefaultLibs: " << nodefaultlibs.GetCount());
	for(i = 0; i < nodefaultlibs.GetCount(); i++)
		RLOG(NFormat("nodefaultlib[%d] = %s", i, nodefaultlibs[i]));
	int atom_size = (16 + 16) * atoms.GetCount();
	for(i = 0; i < atoms.GetCount(); i++)
		atom_size += atoms[i].GetLength();
	RLOG("#atoms = " << atoms.GetCount() << ", size = " << atom_size);
	RLOG("#globals = " << globals.GetCount() << ", size = " << globals.GetCount() * (sizeof(Symbol) + 16));
	for(i = 0; i < globals.GetCount(); i++)
		RLOG(NFormat("[%3>d]: %30<s = %s", i, atoms[globals.GetKey(i)], globals[i].ToString()));
	RLOG("#objects = " << objects.GetCount());
//	for(i = 0; i < objects.GetCount(); i++)
//		objects[i].Dump();
}

/*
int LinkJob::AddGlobal(int atom, const Symbol& symbol)
{
	globals.Add(atom, symbol);

	int pos = globals.Find(atom);
	if(pos < 0) {
		pos = globals.GetCount();
		globals.Add(atom, symbol);
		return pos;
	}
	Symbol& old_symbol = globals[pos];
//	if(old_symbol.storage_class != symbol.storage_class)
//		PutStdOut(NFormat("%s: storage class mismatch (%02x / %02x)", atoms[atom], old_symbol.storage_class, symbol.storage_class));
	if(old_symbol.section == 0)
		old_symbol = symbol;
	else if(symbol.section != 0) { // duplicate / comdat
		if(old_symbol.type != symbol.type)
			PutStdOut(NFormat("%s: symbol type mismatch (%04x / %04x)", atoms[atom], old_symbol.type, symbol.type));
		if(old_symbol.section > 0 && symbol.section > 0) {
			ObjectFile::Section& sec1 = objects[old_symbol.object].sections[old_symbol.section];
			ObjectFile::Section& sec2 = objects[symbol.object].sections[symbol.section];
			if((sec1.flags & sec2.flags) & COFF_IMAGE_SCN_LNK_COMDAT) {
				if(sec1.comdat_packing != sec2.comdat_packing)
					PutStdOut(NFormat("%s: comdat packing style mismatch: %d / %d",
						atoms[atom], sec1.comdat_packing, sec2.comdat_packing));
				switch(sec2.comdat_packing) {
				case COFF_IMAGE_COMDAT_SELECT_NODUPLICATES:
					break;

				case COFF_IMAGE_COMDAT_SELECT_ANY:
					return pos;

				case COFF_IMAGE_COMDAT_SELECT_SAME_SIZE:
					if(sec2.size == sec1.size)
						return pos;
					break;

				case COFF_IMAGE_COMDAT_SELECT_EXACT_MATCH:
					if(sec2.size == sec1.size && sec2.exact_comdat == sec1.exact_comdat)
						return pos;
					break;

				case COFF_IMAGE_COMDAT_SELECT_ASSOCIATIVE:
					break;

				case COFF_IMAGE_COMDAT_SELECT_LARGEST:
					if(sec2.size > sec1.size)
						old_symbol = symbol;
					return pos;
				}
			}
		}

		PutStdOut(NFormat("%s: duplicate (%s:%d / %s:%d)", atoms[atom],
			objects[old_symbol.object].file, old_symbol.section,
			objects[symbol.object].file, symbol.section));
	}
	return pos;
}
*/

void LinkJob::ReadDefaultLibs()
{
	LTIMING("LinkJob::ReadDefaultLibs");
	for(int i = 0; i < defaultlibs.GetCount(); i++)
		if(nodefaultlibs.Find(defaultlibs[i]) < 0) {
			if(dump_flags & DUMP_OBJECTS)
				PutStdOut(NFormat("defaultlib: %s", defaultlibs[i]));
			LoadFile(defaultlibs[i], true);
		}
}

void LinkJob::SetEntryPoint()
{
	if(IsNull(entrypoint))
		if(make_dll)
			entrypoint = "_DllMainCRTStartup";
		else if(subsystem == COFF_IMAGE_SUBSYSTEM_WINDOWS_GUI || subsystem == COFF_IMAGE_SUBSYSTEM_WINDOWS_CE_GUI)
			entrypoint = "_WinMainCRTStartup";
		else
			entrypoint = "_mainCRTStartup";
	entrypoint_atom = Atomize(entrypoint);
}

void LinkJob::SetExportSymbols()
{
	if(exports.IsEmpty() && make_dll) { // collect all globals
		int ordinal = 0;
		for(int i = 0; i < globals.GetCount(); i++) {
			atom_t gatom = globals.GetKey(i);
			const Symbol& sym = globals[i];
			if(gatom != entrypoint_atom && sym.external && objects[sym.obj_sec.object].export_symbols && sym.obj_sec.section >= 0) {
				String name = atoms[gatom];
				const char *p = name;
				if(*p == '_')
					p++;
				exports.Add(gatom, Export(p, ++ordinal));
			}
		}
	}
}

void LinkJob::SetRootSymbols()
{
	AddCollect(entrypoint_atom);
	for(int i = 0; i < exports.GetCount(); i++)
		AddCollect(exports.GetKey(i));
}

void LinkJob::CollectObject(int index)
{
	const ObjectFile& of = objects[index];
	Index<ObjSec> osv;
	for(int t = 0; t < of.sections.GetCount(); t++)
		if(of.sections[t].autocollect)
			osv.Add(ObjSec(index, t));
	CollectSectionSymbols(osv, 0);
}

void LinkJob::CollectSectionSymbols(Index<ObjSec>& collect_objsec, int referer_index)
{
	LTIMING("LinkJob::CollectSectionSymbols");

	int ref = globals.GetKey(referer_index), setref = ref;
	for(int si = 0; si < collect_objsec.GetCount(); si++) {
		ObjSec objsec = collect_objsec[si];
		ObjectFile& of = objects[objsec.object];
		if(!of.collected) {
			of.collected = true;
			for(int s = 0; s < of.sections.GetCount(); s++)
				if(of.sections[s].autocollect) {
					if(dump_flags & DUMP_COLLECTOR)
						PutStdOut(NFormat("%s: autocollect section %d, %s", AsString(of), s, atoms[of.sections[s].name_atom]));
					collect_objsec.FindAdd(ObjSec(objsec.object, s));
				}
		}

		if(objsec.section < 0)
			continue;
		ObjectFile::Section& ofsec = of.sections[objsec.section];
		if(ofsec.sec_atom == debug_atom || ofsec.comdat_state == ObjectFile::COM_TRASHED) {
//			if(si == 0)
//				referer_index = ofsec.comdat_forward;
			continue;
		}

		bool was_used = ofsec.used;

//		LTIMING("LinkJob::CollectSectionSymbols / unused section");
		if(ofsec.flags & COFF_IMAGE_SCN_LNK_COMDAT)
			ofsec.comdat_state = ObjectFile::COM_SELECTED;
/*
		if(ofsec.comdat_state != ObjectFile::COM_SELECTED && ()) {
//			LTIMING("LinkJob::CollectSectionSymbols / COMDAT");
			Vector<int> candidates;
			for(int f = globals.Find(ofsec.name_atom); f >= 0; f = globals.FindNext(f))
				candidates.Add(f);
			if(dump_flags & DUMP_COLLECTOR)
				PutStdOut(NFormat("\tCOMDAT section (%d candidates)", candidates.GetCount()));
			int chosen = -1;
			int current = -1;
			if(candidates.GetCount() > 1) {
				for(int i = 0; i < candidates.GetCount(); i++) {
					ObjSec otheros = globals[candidates[i]].obj_sec;
					if(otheros == objsec) { // current section
						current = i;
						continue;
					}
					ObjectFile& otherof = objects[otheros.object];
					ObjectFile::Section& othersec = otherof.sections[otheros.section];
					if(othersec.used) {
						chosen = i;
						break;
					}
					if(!(othersec.flags & COFF_IMAGE_SCN_LNK_COMDAT)) {
						puts(NFormat("%s: %s is not a COMDAT, whereas %s:%d is (non-COMDAT selected)",
							atoms[ofsec.name_atom], FormatSection(otheros),
							AsString(of), objsec.section));
						chosen = i;
					}
					if(othersec.comdat_packing != ofsec.comdat_packing) {
						PutStdOut(NFormat("%s: %s has COMDAT packing style %d, %s has %d",
							atoms[ofsec.name_atom],
							FormatSection(otheros), othersec.comdat_packing,
							FormatSection(objsec), ofsec.comdat_packing));
					}
					switch(ofsec.comdat_packing) {
					case COFF_IMAGE_COMDAT_SELECT_NODUPLICATES:
						PutStdOut(NFormat("%s: duplicate COMDAT definitions %s / %s",
							atoms[ofsec.name_atom], FormatSection(otheros),
							FormatSection(objsec)));
						break;

					case COFF_IMAGE_COMDAT_SELECT_ANY:
						break;

					case COFF_IMAGE_COMDAT_SELECT_SAME_SIZE:
						if(othersec.size != ofsec.size)
							PutStdOut(NFormat("%s: COMDAT size mismatch: %s - %d B, %s - %d B",
								atoms[ofsec.name_atom],
								FormatSection(otheros), othersec.size,
								FormatSection(objsec), ofsec.size));

					case COFF_IMAGE_COMDAT_SELECT_LARGEST:
						if(chosen < 0 && othersec.size > ofsec.size
						|| chosen >= 0 && othersec.size > GetSection(globals[candidates[chosen]].obj_sec).size)
							chosen = i;
						break;

					case COFF_IMAGE_COMDAT_SELECT_EXACT_MATCH:
						if(othersec.size != ofsec.size
						|| memcmp(othersec.section_data.Begin(), ofsec.section_data.Begin(), ofsec.size))
							PutStdOut(NFormat("%s: COMDAT data mismatch: %s / %s",
								atoms[ofsec.name_atom], FormatSection(otheros),
								FormatSection(objsec)));
						break;

					case COFF_IMAGE_COMDAT_SELECT_ASSOCIATIVE:
						break;

					}
				}
				if(chosen >= 0) {
					int candidate = candidates[chosen];
					if(si == 0)
						referer_index = candidate;
					ObjSec otheros = globals[candidates[chosen]].obj_sec;
					for(int i = 0; i < candidates.GetCount(); i++)
						if(i != chosen) {
							ObjectFile::Section& othersec = GetSection(globals[candidates[i]].obj_sec);
							othersec.comdat_state = ObjectFile::COM_TRASHED;
							othersec.comdat_forward = candidate;
						}
					if(dump_flags & DUMP_COLLECTOR) {
						ObjectFile& otherof = objects[otheros.object];
						puts(NFormat("\tCOMDAT section %s discarded, %s selected",
							FormatSection(objsec), FormatSection(otheros)));
					}
					GetSection(otheros).comdat_state = ObjectFile::COM_SELECTED;
					collect_objsec.FindAdd(otheros);
					continue;
				}
				else if(current >= 0) {
					int candidate = candidates[current];
					for(int i = 0; i < candidates.GetCount(); i++)
						if(i != current) {
							ObjSec otheros = globals[candidates[i]].obj_sec;
							ObjectFile::Section& othersec = GetSection(otheros);
							othersec.comdat_state = ObjectFile::COM_TRASHED;
							othersec.comdat_forward = candidate;
						}
				}
			}
		}
*/

		of.used_any = ofsec.used = true;

		if(setref) {
			for(int rs = global_obj_sec_index.Find(objsec); rs >= 0; rs = global_obj_sec_index.FindNext(rs))
				if(globals.GetKey(rs) == setref) {
					globals[rs].linked = globals[rs].used = true;
					setref = 0;
					break;
				}
		}

		if(!was_used) {
			if(dump_flags & DUMP_COLLECTOR)
				PutStdOut(NFormat("%s: collecting section %d (%d B) %s (referer = %s)",
					AsString(of), objsec.section, ofsec.size, atoms[ofsec.name_atom], atoms[globals.GetKey(referer_index)]));
			for(int rs = global_obj_sec_index.Find(objsec); rs >= 0; rs = global_obj_sec_index.FindNext(rs)) {
				int f = collected_symbols.Find(globals.GetKey(rs));
				globals[rs].linked = true;
				if(f >= 0 && collected_symbols[f] < 0) {
					globals[rs].used = true;
					collected_symbols[f] = rs;
				}
//				bool u = (globals.GetKey(rs) == ref);
//				if(u)
//					globals[rs].used = true;
				if(dump_flags & DUMP_COLLECTOR)
					PutStdOut(NFormat("\tdefines: %-30s VAL %08x TYP %04x %[1:*]s",
						atoms[globals.GetKey(rs)], globals[rs].value, globals[rs].type, globals[rs].used));
			}
		}

		if(objsec.section <= 0)
			continue;

		if(!was_used) {
			for(int ca = of.comdat_assoc.Find(objsec.section); ca >= 0; ca = of.comdat_assoc.FindNext(ca)) {
	//			LTIMING("LinkJob::CollectSectionSymbols / COMDAT_ASSOC");
				ObjSec cca(of.index, of.comdat_assoc[ca]);
				if(collect_objsec.Find(cca) < 0) {
					collect_objsec.Add(cca);
					if(!was_used && (dump_flags & DUMP_COLLECTOR))
						PutStdOut(NFormat("\tassoc comdat: %d", cca.section));
				}
			}
		}

		int sec_end = of.ref_sections.GetCount();
		int ext_end = of.ref_externals.GetCount();
		if(objsec.section + 1 < of.sections.GetCount()) {
			sec_end = of.sections[objsec.section + 1].ref_sec_index;
			ext_end = of.sections[objsec.section + 1].ref_ext_index;
		}
		if(ofsec.type == ObjectFile::SEC_STD) {
//			LTIMING("LinkJob::CollectSectionSymbols / references + xrefs + collector");
			if(!was_used) {
//				LTIMING("LinkJob::CollectSectionSymbols / section references");
				for(int x = ofsec.ref_sec_index; x < sec_end; x++) {
					int isec = of.ref_sections[x];
					const ObjectFile::Section& sec = of.sections[isec];
					if(!sec.used) {
						collect_objsec.FindAdd(ObjSec(of.index, isec));
						if(!was_used && (dump_flags & DUMP_COLLECTOR))
							PutStdOut(NFormat("\tintra-object reference to section %d", isec));
					}
				}
//				LTIMING("LinkJob::CollectSectionSymbols / symbol references");
				for(int x = ofsec.ref_ext_index; x < ext_end; x++) {
					int sx = obj_static_global_index.Get(Point(objsec.object, of.ref_externals[x]), -1);
					if(sx >= 0) { // static global
						const ObjectFile::Section& sec = GetSection(globals[sx].obj_sec);
						if(!sec.used) {
							collect_objsec.FindAdd(globals[sx].obj_sec);
							if(!was_used && (dump_flags & DUMP_COLLECTOR))
								PutStdOut(NFormat("\tintra-object reference to static %s", atoms[globals.GetKey(sx)]));
						}
					}
					else
						AddCollect(of.ref_externals[x]);
				}
			}
			if(write_xref) {
				for(int x = ofsec.ref_ext_index; x < ext_end; x++)
					collected_referer.GetAdd(of.ref_externals[x]).FindAdd(globals.GetKey(referer_index));
			}
			if((dump_flags & DUMP_COLLECTOR) && !was_used) {
				for(int x = ofsec.ref_ext_index; x < ext_end; x++)
					PutStdOut(NFormat("\treferences: %s", atoms[of.ref_externals[x]]));
			}
		}
	}
}

void LinkJob::CollectSymbols()
{
//	int xxx = Atomize("__crtBreakAlloc");
	bool error = false;
	LTIMING("LinkJob::CollectSymbols");
	for(int i = 0; i < objects.GetCount(); i++)
		if(objects[i].filetype == ObjectFile::DIRECT)
			CollectObject(i);
/* {
			const ObjectFile& of = objects[i];
			Index<ObjSec> osv;
			for(int t = 0; t < of.sections.GetCount(); t++)
				if(of.sections[t].autocollect)
					osv.Add(ObjSec(i, t));
			CollectSectionSymbols(osv, 0);
		}
*/
	Index<int> undefined;
	Vector<ObjSec> undef_ref;
	for(int i = 0; i < collected_symbols.GetCount(); i++) {
		atom_t ref = collected_symbols.GetKey(i);
//		if(ref == CTOR_LIST_atom || ref == _CTOR_LIST_atom || ref == CTOR_LIST_END_atom
//		|| ref == DTOR_LIST_atom || ref == _DTOR_LIST_atom || ref == DTOR_LIST_END_atom)
//			__asm int 3
		if(dump_flags & DUMP_COLLECTOR)
			PutStdOut(atoms[ref] + ": collecting symbol");
//		if(ref == xxx)
//			__asm int 3
		int a, af = globals.Find(ref);
		if(af < 0) {
			if((a = weak_externals.Find(ref)) >= 0) {
				if((af = weak_externals.FindNext(a)) >= 0)
					PutStdOut(NFormat("%s: weak external has multiple aliases (%s, %s)",
						atoms[ref], atoms[a], atoms[af]));
				AddCollect(weak_externals[a]);
				if(write_xref)
					collected_referer.GetAdd(weak_externals[a]).FindAdd(ref);
				continue;
			}
			if(undefined.Find(collected_symbols.GetKey(i)) < 0) {
				undefined.Add(collected_symbols.GetKey(i));
				undef_ref.Add(ext_obj_sec.Get(ref, Null));
			}
			continue;
		}
		int collect_global = -1;
		int collect_byfile = -1;
//		ObjectFile *collect_file = NULL;
//		Index<int> collect_sections;
		for(a = af; a >= 0; a = globals.FindNext(a)) {
			if(globals[a].used) {
				collect_global = a;
				break;
			}
			int secno = globals[a].obj_sec.section;
			if(secno > 0) {
				ObjectFile& of = objects[globals[a].obj_sec.object];
				if(of.used_any) {
					collect_byfile = a;
	//				globals[a].used = true;
	//				collected_symbols[i] = a;
	//				collect_file = &of;
	//				collect_sections.FindAdd(secno);
				}
			}
		}
		if(collect_global < 0)
			collect_global = collect_byfile;
		if(collect_global < 0) {
//			if(ref == Atomize("__acmdln"))
//				__asm int 3
			Index<int> dflib_candidates;
			Index<int> lib_candidates;
			Index<int> full_candidates;
			Index<int> direct_candidates;
			Index<int> inner_candidates;
			for(a = af; a >= 0; a = globals.FindNext(a)) {
				int atom = globals.GetKey(a);
				Symbol& global = globals[a];
				if(global.obj_sec.object < OBJ_FIRST) {
					inner_candidates.Add(a);
					continue;
				}
				const ObjectFile& of = objects[global.obj_sec.object];
				if(of.used_any)
					inner_candidates.Add(a);
				else if(of.filetype == of.DIRECT)
					direct_candidates.Add(a);
				else if(of.filetype == of.FULL_PATH)
					full_candidates.Add(a);
				else if(of.filetype == of.LIB_PATH)
					lib_candidates.Add(a);
				else
					dflib_candidates.Add(a);
			}
			if(inner_candidates.IsEmpty())
				inner_candidates = direct_candidates;
			if(inner_candidates.IsEmpty())
				inner_candidates = full_candidates;
			if(inner_candidates.IsEmpty())
				inner_candidates = lib_candidates;
			if(inner_candidates.IsEmpty())
				inner_candidates = dflib_candidates;
			if(inner_candidates.GetCount() > 1) {
				for(a = 1; a < inner_candidates.GetCount(); a++) {
					bool matched = false;
					const Symbol& sym1 = globals[inner_candidates[0]];
					const Symbol& sym2 = globals[inner_candidates[a]];
					if(sym1.type != sym2.type) {
/*						PutStdOut(NFormat("%s: symbol type mismatch (%s:%d:%04x / %s:%d:%04x)",
							atoms[ref],
							sym1.obj_sec.object >= 0 ? AsString(objects[sym1.obj_sec.object]) : String("(linker)"),
							sym1.obj_sec.section, sym1.type,
							sym2.obj_sec.object >= 0 ? AsString(objects[sym2.obj_sec.object]) : String("(linker)"),
							sym2.obj_sec.section, sym2.type));
*/					}
					else if((sym1.type == COFF_IMAGE_SYM_TYPE_IMPORT_NAME || sym1.type == COFF_IMAGE_SYM_TYPE_IMPORT_ORDINAL
						|| sym1.type == COFF_IMAGE_SYM_TYPE_IMPORT_STUB) && sym2.type == sym1.type) {
						const ObjectFile::Import& imp1 = objects[sym1.obj_sec.object].imports[sym1.value];
						const ObjectFile::Import& imp2 = objects[sym2.obj_sec.object].imports[sym2.value];
						bool m = true;
						if(imp1.imp_atom != imp2.imp_atom) {
							m = false;
							PutStdOut(NFormat("%s: import name mismatch (%s, %s)", atoms[ref], atoms[imp1.imp_atom], atoms[imp2.imp_atom]));
						}
						if(sym1.type == COFF_IMAGE_SYM_TYPE_IMPORT_ORDINAL && imp1.ordinal_hint != imp2.ordinal_hint) {
							m = false;
							PutStdOut(NFormat("%s: import library ordinal mismatch (%d, %d)", atoms[ref], imp1.ordinal_hint, imp2.ordinal_hint));
						}
						if(m)
							matched = true;
					}
					else if(sym1.obj_sec.section > 0 && sym2.obj_sec.section > 0) {
						ObjectFile::Section& sec1 = GetSection(sym1.obj_sec);
						ObjectFile::Section& sec2 = GetSection(sym2.obj_sec);
						if((sec1.flags | sec2.flags) & COFF_IMAGE_SCN_LNK_COMDAT)
							matched = true;
					}
					else if(sym1.obj_sec.section == 0 && sym2.obj_sec.section == 0 && sym1.value == sym2.value)
						matched = true;
					if(!matched) {
						PutStdOut(NFormat(
							"%s: duplicate\n"
							"-> used:      %s\n"
							"-> discarded: %s",
							atoms[ref], FormatSection(sym1.obj_sec), FormatSection(sym2.obj_sec)));
					}
				}
			}
			if(!inner_candidates.IsEmpty()) {
//				collected_symbols[i] = inner_candidates[0];
				int chosen = inner_candidates[0];
				Symbol& sym = globals[chosen];
				if(sym.obj_sec.object >= 0 && sym.obj_sec.section > 0)
					collect_global = chosen;
//				sym.used = true;
//				if(sym.obj_sec.object >= 0) {
//					ObjectFile& fo = objects[sym.obj_sec.object];
//					if(fo.dll_atom)
//						__asm int 3
//					if(fo.dll_atom)
//						PutStdOut(NFormat("collecting DLL import %s", atoms[ref]));
//					if(sym.obj_sec.section > 0)
//						collect_global = chosen;
//					{
//						collect_file = &fo;
//						collect_sections.FindAdd(sym.obj_sec.section);
//					}
/*
					else if(sym.obj_sec.section == 0) { // anon comdat
						int comdat_align = 1;
						while(comdat_align < 16 && !(sym.value & comdat_align))
							comdat_align <<= 1;
						int v = (fo.sections[0].size + comdat_align - 1) & -comdat_align;
						fo.sections[0].size = v + sym.value;
						fo.used_any = fo.sections[0].used = true;
						sym.value = v;
					}
*///				}
				if((sym.obj_sec.section || sym.obj_sec.object < 0) && collect_global < 0)
					globals[collected_symbols[i] = chosen].used = true;
			}
		}
		if(collect_global >= 0) // && !collect_file->dll_atom)
		{
			ObjSec os = globals[collect_global].obj_sec;
//			if(os.object == OBJ_CTOR)
//				__asm int 3
			if(os.object >= 0) {
				Index<ObjSec> objsec;
				objsec.Add(os);
				CollectSectionSymbols(objsec, collect_global);
/*
				if(!globals[collect_global].used) {
					int i;
					for(i = globals.Find(ref); i >= 0 && !globals[i].used; i = globals.FindNext(i))
						;
					collect_global = i;
					if(i >= 0)
						while((i = globals.FindNext(i)) >= 0 && !globals[i].used)
							;
					if(i >= 0)
						puts(NFormat("%s: duplicate in %s (used, %s (discarded)",
							atoms[ref], FormatSection(globals[collect_global].obj_sec),
								FormatSection(globals[i].obj_sec)));
				}
				if(collect_global < 0)
					puts(NFormat("%s: undefined", atoms[ref]));
*/
			}
			else
				collected_symbols[i] = collect_global;
		}
	}

	for(int i = 0; i < collected_symbols.GetCount(); i++)
		if(collected_symbols[i] < 0) {
			int atom = collected_symbols.GetKey(i);
			int f = globals.Find(atom);
			int anon = -1;
			while(f >= 0 && !globals[f].linked) {
				if(anon < 0) {
					ObjSec objsec = globals[f].obj_sec;
					if(objsec.section == 0 && objsec.object >= OBJ_FIRST)
						anon = f;
				}
				f = globals.FindNext(f);
			}
			if(f >= 0) {
				int g = f;
				while((g = globals.FindNext(g)) >= 0 && !globals[g].used)
					;
				if(g >= 0) {
					if(!(GetSection(globals[g].obj_sec).flags & GetSection(globals[f].obj_sec).flags
					& COFF_IMAGE_SCN_LNK_COMDAT)) {
						puts(NFormat("%s: duplicate\n"
							"-> used:      %s\n"
							"-> discarded: %s",
							atoms[atom], FormatSection(globals[f].obj_sec), FormatSection(globals[g].obj_sec)));
						globals[g].used = false;
					}
				}
				else if(dump_flags & DUMP_COLLECTOR)
					puts(NFormat("%s: delayed collect from %s", atoms[atom], FormatSection(globals[f].obj_sec)));
				collected_symbols[i] = f;
				globals[f].used = true;
			}
			else if(anon >= 0) {
				Symbol& sym = globals[anon];
				ObjectFile& of = objects[sym.obj_sec.object];
				ObjectFile::Section& ofsec = of.sections[ObjectFile::SEC_ANON_COMDAT];
				int comdat_align = 1;
				while(comdat_align < 16 && !(sym.value & comdat_align))
					comdat_align <<= 1;
				int v = (ofsec.size + comdat_align - 1) & -comdat_align;
				ofsec.size = v + sym.value;
				of.used_any = ofsec.used = true;
				sym.value = v;
				sym.used = sym.linked = true;
				collected_symbols[i] = anon;
//				puts(NFormat("%s: anon comdat collect from %d", atoms[globals.GetKey(anon)], sym.obj_sec.object));
				fflush(stdout);
			}
		}

	for(int i = 0; i < weak_externals.GetCount(); i++) {
		int f = collected_symbols.Find(weak_externals.GetKey(i));
		if(f >= 0 && collected_symbols[f] < 0)
			collected_symbols[f] = collected_symbols.Get(weak_externals[i], -1);
	}

	if(idata_last >= 0) {
		Index<ObjSec> collect;
		collect.Add(ObjSec(OBJ_CTOR, OBJC_IDATA_HOLE));
		if(!idata_null_found)
			collect.Add(ObjSec(OBJ_CTOR, OBJC_IDATA_NULL));
		CollectSectionSymbols(collect, 0);
	}

	if(!undefined.IsEmpty()) {
		Vector<String> untext;
		untext.SetCount(undefined.GetCount());
		int u;
		for(u = 0; u < undefined.GetCount(); u++)
			untext[u] = atoms[undefined[u]];
		Vector<int> order = GetSortOrder(untext);
		PutStdOut(NFormat("%d undefined symbol(s):", untext.GetCount()));
		for(u = 0; u < order.GetCount(); u++) {
			int o = order[u];
			String ident = untext[o];
			ObjSec obj_sec = ext_obj_sec.Get(undefined[o], Null);
			if(!IsNull(obj_sec)) {
				String os_globals;
				for(int t = global_obj_sec_index.Find(obj_sec); t >= 0; t = global_obj_sec_index.FindNext(t))
					if(os_globals.GetLength() >= 80) {
						os_globals << "; ...";
						break;
					}
					else
						os_globals << "; " << DemangleName(atoms[globals.GetKey(t)], linkermode == MODE_GNULD ? MANGLING_GCC : MANGLING_MSC);
				PutStdOut(NFormat("%s (referenced from %s%s)", ident, FormatSection(obj_sec), os_globals));
			}
			else
				PutStdOut(ident);
		}
		error = true;
	}

	if(dump_flags & DUMP_STAT) {
		PutStdOut(NFormat("#objects = %d", objects.GetCount()));
		int atom_size = (16 + 16) * atoms.GetCount();
		int i;
		for(i = 0; i < atoms.GetCount(); i++)
			atom_size += atoms[i].GetLength();
		PutStdOut(NFormat("#atoms = %d, size = %d", atoms.GetCount(), atom_size));
		int used_globals = 0;
		for(i = 0; i < globals.GetCount(); i++)
			if(globals[i].used)
				used_globals++;
		PutStdOut(NFormat("#globals = %d (%d used), size = %d", globals.GetCount(), used_globals,
			(int)(globals.GetCount() * (sizeof(Symbol) + 16))));
	}

	if(error)
		throw Exc("linking aborted due to fatal errors");

//	FixupAnonComdats();

//	CheckDuplicates();
}

/*
void LinkJob::FixupAnonComdats()
{
	for(int i = 0; i < collected_symbols.GetCount(); i++) {
		atom_t atom = collected_symbols.GetKey(i);
		int iglo = collected_symbols[i];
		Symbol& sym = globals[iglo];
		if(sym.obj_sec.object >= OBJ_FIRST && sym.obj_sec.section == ObjectFile::SEC_ANON_COMDAT) {
			int f = -1;
			if(globals.FindPrev(iglo) >= 0 || globals.FindNext(iglo) >= 0) {
				String name = atoms[atom];
//				PutStdOut(NFormat("anon comdat: %s (%s)", name, FormatSection(sym.obj_sec)));
				for(f = globals.Find(atom); f >= 0; f = globals.FindNext(f))
					if(f != iglo) {
						const Symbol& sym = globals[f];
//						PutStdOut(NFormat("-> %s", FormatSection(sym.obj_sec)));
						if(sym.linked && sym.obj_sec.section > 0)
							break;
					}
			}
			if(f >= 0) { // remap to non-comdat
				if(dump_flags & DUMP_COMDAT_REMAPS)
					PutStdOut(NFormat("%s: COMDAT remapped to %s", atoms[atom], FormatSection(globals[f].obj_sec)));
				collected_symbols[i] = f;
				sym.used = false;
				globals[f].used = true;
			}
			else {
				ObjectFile& of = objects[sym.obj_sec.object];
				ObjectFile::Section& ofsec = of.sections[ObjectFile::SEC_ANON_COMDAT];
				int comdat_align = 1;
				while(comdat_align < 16 && !(sym.value & comdat_align))
					comdat_align <<= 1;
				int v = (ofsec.size + comdat_align - 1) & -comdat_align;
				ofsec.size = v + sym.value;
				of.used_any = ofsec.used = true;
				sym.value = v;
			}
		}
	}
}
*/

/*
void LinkJob::CheckDuplicates()
{
	bool error = false;
	for(int i = 0; i < globals.GetCount(); i++)
		if(globals[i].type != COFF_IMAGE_SYM_TYPE_SECTION && globals.FindPrev(i) < 0) {
			int atom = globals.GetKey(i);
			const Symbol *first = NULL;
			for(int f = i; f >= 0; f = globals.FindNext(f)) {
				const Symbol& global = globals[f];
				if(!global.used || global.obj_sec.object < 0)
					continue;
				const ObjectFile& object = objects[global.obj_sec.object];
				if(!first) {
					first = &global;
					continue;
				}
				if(first -> type == COFF_IMAGE_SYM_TYPE_SECTION && global.type == COFF_IMAGE_SYM_TYPE_SECTION)
					continue;
				if((first->type == COFF_IMAGE_SYM_TYPE_IMPORT_NAME || first->type == COFF_IMAGE_SYM_TYPE_IMPORT_ORDINAL)
				&& global.type == first->type) {
					const ObjectFile::Import& imp1 = objects[first->obj_sec.object].imports[first->value];
					const ObjectFile::Import& imp2 = objects[global.obj_sec.object].imports[global.value];
					if(imp1.imp_atom != imp2.imp_atom)
						PutStdOut(NFormat("%s: import name mismatch (%s, %s)", atoms[atom], atoms[imp1.imp_atom], atoms[imp2.imp_atom]));
					if(first->type == COFF_IMAGE_SYM_TYPE_IMPORT_ORDINAL && imp1.ordinal_hint != imp2.ordinal_hint)
						PutStdOut(NFormat("%s: import ordinal mismatch (%d, %d)", atoms[atom], imp1.ordinal_hint, imp2.ordinal_hint));
					continue;
				}
				error = true;
				PutStdOut(NFormat("%s: duplicate (%s / %s)", atoms[atom],
					FormatSection(first->obj_sec), FormatSection(global.obj_sec)));
			}
		}
//	if(error)
//		throw Exc("duplicates found, linking aborted");
}
*/

void LinkJob::CollectImports()
{
	for(int i = 0; i < dll_objects.GetCount(); i++) {
		ObjectFile& of = objects[dll_objects[i]];
		ASSERT(of.dll_atom);
		ASSERT(of.used_imports.IsEmpty());
		if(of.used_any) {
			ObjectFile::Section& stubsec = of.sections[ObjectFile::SEC_DLL_STUBS];
			ObjectFile::Section& namesec = of.sections[ObjectFile::SEC_DLL_NAMES];
			ObjectFile::Section& impsec  = of.sections[ObjectFile::SEC_DLL_IMPORTS];
			ObjectFile::Section& bimpsec = of.sections[ObjectFile::SEC_DLL_BOUND];
			ObjectFile::Section& descsec = of.sections[ObjectFile::SEC_DLL_DESCRIPTOR];
//			ObjectFile::Section& nullsec = of.sections[ObjectFile::SEC_DLL_NULL_DESC];
			String dllfn = ToUpper(GetFileName(of.object_file));
			CatN(namesec.section_data, dllfn.GetLength() + 1, dllfn);
			if(namesec.section_data.GetCount() & 1)
				namesec.section_data.Add(0);
			namesec.size = namesec.section_data.GetCount();
			namesec.used = true;
			int g;
			for(g = global_obj_sec_index.Find(ObjSec(of.index, ObjectFile::SEC_DLL_STUBS)); g >= 0; g = global_obj_sec_index.FindNext(g)) {
				Symbol& stubimp = globals[g];
				if(!stubimp.used)
					continue;
//				if(of.used_stubs.Find(stubimp.value) < 0)
//					PutStdOut(NFormat("stub import[%d] = %s", of.used_stubs.GetCount(), atoms[globals.GetKey(g)]));
				of.used_imports.FindAdd(stubimp.value);
				stubimp.value = imp_stub_size * of.used_stubs.FindAdd(stubimp.value);
				stubsec.used = impsec.used = true;
			}
			for(g = global_obj_sec_index.Find(ObjSec(of.index, ObjectFile::SEC_DLL_BOUND)); g >= 0; g = global_obj_sec_index.FindNext(g)) {
				Symbol& imp = globals[g];
				if(!imp.used)
					continue;
				ObjectFile::Import& import = of.imports[imp.value];
				of.used_imports.Add(imp.value);
				imp.value = imp_entry_size * (1 + of.used_imports.FindAdd(imp.value));
				impsec.used = true;
			}
			stubsec.size = imp_stub_size * of.used_stubs.GetCount();
			if(!of.used_imports.IsEmpty()) {
				ASSERT(impsec.used);
				used_dll_objects.Add(of.index);
				impsec.used = bimpsec.used = true;
				impsec.size = bimpsec.size = imp_entry_size * (of.used_imports.GetCount() + 1);
				impsec.section_data.SetCount(impsec.size, 0);
				for(g = 0; g < of.used_imports.GetCount(); g++) {
					const ObjectFile::Import& oi = of.imports[of.used_imports[g]];
					int ival = oi.ordinal_hint | 0x80000000;
					if(oi.imp_atom) { // create hint/name table entry
						ival = namesec.size;
						String ia = atoms[oi.imp_atom];
						int offset = CatN0(namesec.section_data, ((ia.GetLength() + 4) & -2));
						PokeIW(&namesec.section_data[offset], oi.ordinal_hint);
						memcpy(&namesec.section_data[offset + 2], ia, ia.GetLength());
						namesec.size = namesec.section_data.GetCount();
					}
					byte *ient = impsec.section_data.GetIter(imp_entry_size * (g + 1));
					PokeIL(ient, ival);
				}
				bimpsec.section_data <<= impsec.section_data;
				descsec.used = true;
				descsec.size = sizeof(COFF_IMAGE_IMPORT_DESCRIPTOR);
/*
				if(used_dll_objects.GetCount() == 1) {
					nullsec.used = true;
					nullsec.size = sizeof(COFF_IMAGE_IMPORT_DESCRIPTOR);
				}
*/
				if(dump_flags & DUMP_IMPORT) {
					PutStdOut(NFormat("%s: %d imports used; %d using stub; %d bytes in name table",
						of.object_file, of.used_imports.GetCount(), of.used_stubs.GetCount(), namesec.size));
					for(g = 0; g < of.used_imports.GetCount(); g++) {
						int uimp = of.used_imports[g];
						const ObjectFile::Import& ofimp = of.imports[uimp];
						if(ofimp.app_atom == ofimp.imp_atom)
							PutStdOut(NFormat("\t(%d) %s: %[1:stub]s",
								ofimp.ordinal_hint, atoms[ofimp.imp_atom],
								of.used_stubs.Find(uimp) >= 0));
						else
							PutStdOut(NFormat("\t(%d) %s -> %s: %[1:stub]s",
								ofimp.ordinal_hint, atoms[ofimp.app_atom], atoms[ofimp.imp_atom],
								of.used_stubs.Find(uimp) >= 0));
					}
				}
			}
		}
	}
}

void LinkJob::CollectExports()
{
	if(exports.IsEmpty())
		return;
	ObjectFile& ofe = objects[OBJ_EXPORT];
	ObjectFile::Section& dir = ofe.sections[OBJE_DIRECTORY];
	ObjectFile::Section& adr = ofe.sections[OBJE_ADDRESS_TABLE];
	ObjectFile::Section& ptr = ofe.sections[OBJE_NAME_POINTERS];
	ObjectFile::Section& ord = ofe.sections[OBJE_ORDINALS];
	ObjectFile::Section& nam = ofe.sections[OBJE_NAMES];

	dir.size = dir.raw_size = sizeof(COFF_IMAGE_EXPORT_DIRECTORY_TABLE);
	COFF_IMAGE_EXPORT_DIRECTORY_TABLE expdir;
	Zero(expdir);

	int min_ordinal, max_ordinal;
	min_ordinal = max_ordinal = exports[0].ordinal;
	Vector<String> named_exports;
	Vector<int> named_ordinals;
	for(int i = 0; i < exports.GetCount(); i++) {
		int e = exports[i].ordinal;
		if(e < min_ordinal) min_ordinal = e;
		if(e > max_ordinal) max_ordinal = e;
		if(!IsNull(exports[i].export_name)) {
			named_exports.Add(exports[i].export_name);
			named_ordinals.Add(e);
		}
	}
	expdir.DateTimeStamp = time(NULL);
	expdir.MajorVersion = major_version;
	expdir.MinorVersion = minor_version;
	expdir.NameRVA = 0;
	String dll_name = ToUpper(GetFileName(outputfile));
	CatN(nam.section_data, dll_name.GetLength() + 1, dll_name);
	expdir.OrdinalBase = min_ordinal;
	expdir.AddressTableEntries = max_ordinal - min_ordinal + 1;
	expdir.NumberOfNamePointers = named_exports.GetCount();

	adr.size = adr.raw_size = 4 * (max_ordinal - min_ordinal + 1);
	adr.section_data.SetCount(adr.size, 0);

	IndexSort(named_exports, named_ordinals, StdLess<String>());
	ptr.size = ptr.raw_size = 4 * named_exports.GetCount();
	ord.size = ord.raw_size = 2 * named_exports.GetCount();
	ptr.section_data.SetCount(ptr.size);
	ord.section_data.SetCount(ord.size);

	for(int i = 0; i < named_exports.GetCount(); i++) {
		PokeIL(ptr.section_data.GetIter(4 * i), nam.section_data.GetCount());
		PokeIW(ord.section_data.GetIter(2 * i), named_ordinals[i] - min_ordinal);
		String exp = named_exports[i];
		CatN(nam.section_data, exp.GetLength() + 1, exp);
	}
	nam.size = nam.raw_size = nam.section_data.GetCount();

	CatN(dir.section_data, dir.size, &expdir);

	if(dump_flags & DUMP_EXPORTS) {
		PutStdOut("Ordinal Export");
		for(int i = 0; i < exports.GetCount(); i++) {
			String ename = exports[i].export_name;
			String sname = atoms[exports.GetKey(i)];
			if(sname == ename)
				PutStdOut(NFormat("%7<d %s", exports[i].ordinal, ename));
			else
				PutStdOut(NFormat("%7<d %30<s %s", exports[i].ordinal, ename, sname));
		}
	}

	CollectObject(OBJ_EXPORT);
}

void LinkJob::CollectSections()
{
	iat2_rva = iat2_end_rva = 0;
	idesc_rva = idesc_end_rva = 0;
	rsrc_rva = rsrc_end_rva = 0;

/*
	if(idata_last >= 0 && !idata_null_found) {
		ObjectFile& of = objects[idata_last];
		ObjectFile::Section& ofsec = of.sections.Add();
//		ObjectFile::Section& prev = of.sections[of.sections.GetCount() - 2];
		ofsec.name_atom = idata_inull_atom;
		ofsec.sec_atom = idata_atom;
		ofsec.size = sizeof(COFF_IMAGE_IMPORT_DESCRIPTOR);
		ofsec.raw_size = 0;
		ofsec.offset = 0;
		ofsec.flags = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA
			| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE;
		ofsec.ref_sec_index = of.ref_sections.GetCount();
		ofsec.ref_ext_index = of.ref_externals.GetCount();
		ofsec.sec_map_index = -1;
		of.used_any = ofsec.used = true;
		ofsec.type = ObjectFile::SEC_INULL;
	}
*/

//	for(i = 0; i < SEC_COUNT; i++)
//		section_map[i].Clear();
/*
	if(debug_info) {
		Section& se = section_map.Add();
		se.obj_sec = Null;
		se.group = GRP_SYMBOLS;
		se.name = ".symbols";
		se.rva = se.rfa = -1;
		se.size = coff_output_symbols.GetCount() * sizeof(COFF_IMAGE_SYMBOL)
			+ coff_output_symbol_strings.GetLength();
	}
*/
	for(int i = 0; i < objects.GetCount(); i++)
		if(objects[i].used_any) {
			const ObjectFile& of = objects[i];
			for(int s = 0; s < of.sections.GetCount(); s++)
				if(of.sections[s].used) {
					const ObjectFile::Section& ofsec = of.sections[s];
					int sec_atom = section_merge.Get(ofsec.sec_atom, ofsec.sec_atom);
					int sx = GRP_NONE;
					if(i == OBJ_EXPORT)
						sx = GRP_EDATA;
					else if(sec_atom == rsrc_atom)
						sx = GRP_RSRC;
					else if(sec_atom == debug_atom)
						sx = GRP_DEBUG;
					else if(sec_atom == idata_atom)
						sx = GRP_IDATA;
					else if(ofsec.flags & COFF_IMAGE_SCN_CNT_CODE)
						sx = GRP_CODE;
					else if(!(ofsec.flags & COFF_IMAGE_SCN_MEM_WRITE))
						sx = GRP_RDATA;
					else if(ofsec.flags & COFF_IMAGE_SCN_CNT_UNINITIALIZED_DATA)
						sx = GRP_UDATA;
					else
						sx = GRP_DATA;
					Section& se = section_map.Add();
					se.obj_sec = ObjSec(i, s);
					se.group = sx;
					se.udata = (ofsec.type == ObjectFile::SEC_ANON_COMDAT
						|| ofsec.type == ObjectFile::SEC_STD && ofsec.offset == 0
						|| (ofsec.flags & (COFF_IMAGE_SCN_CNT_INITIALIZED_DATA
							| COFF_IMAGE_SCN_CNT_UNINITIALIZED_DATA | COFF_IMAGE_SCN_CNT_CODE))
							== COFF_IMAGE_SCN_CNT_UNINITIALIZED_DATA);
//					if(se.udata)
//						puts(NFormat("UDATA[%08x], s0=%d: %s:%d", ofsec.flags,
//							ofsec.type == ObjectFile::SEC_ANON_COMDAT,
//							of.object_file, s));
					se.sec_atom = (sx == GRP_CODE ? text_atom
						: sx == GRP_DATA ? data_atom
						: sx == GRP_RDATA ? rdata_atom
						: sx == GRP_UDATA ? bss_atom : sec_atom);
					se.name = atoms[ofsec.name_atom];
					ASSERT(!se.name.IsEmpty());
					se.size = ofsec.size;
					if(ofsec.sec_atom == idata_atom || ofsec.sec_atom == crt_atom) {
						se.name << ':';
						if(!IsNull(of.library_file))
							se.name << of.library_file << ':';
						se.name << of.object_file;
					}
				}
		}

	ZeroArray(groups);
	Sort(section_map, &LinkJob::Less);
	section_object_index.Reserve(section_map.GetCount());
//	section_obj_sec_index.Reserve(section_map.GetCount());
	int rva = 0; //, rfa = 0;
	int prev_sec = 0;
	int prev_grp = 0;
	for(int i = 0; i < section_map.GetCount(); i++) {
		Section& section = section_map[i];
		int rva_align = image_align, rfa_align = image_align;
		ObjectFile::Section& ofsec = GetSection(section.obj_sec);
//		ofsec_atom = ofsec.name_atom;
		rva_align = CoffGetLengthAlignment(1 << CoffSectionAlignShift(ofsec.flags), ofsec.size);
		rfa_align = rva_align;
//			sec_size = ofsec.size;
		ofsec.sec_map_index = i;
		if(section.sec_atom != prev_sec) {
			section_name_map.Add(prev_sec = section.sec_atom, i);
			rva_align = max(rva_align, image_align);
			rfa_align = max(rfa_align, file_align);
		}
		section.app_section = section_name_map.GetCount() - 1;
		section.rva = rva = (rva + rva_align - 1) & -rva_align;
//		section.rfa = rfa = (rfa + rfa_align - 1) & -rfa_align;
		if(iat2_rva == 0 && ofsec.name_atom == idata_iat2_atom)
			iat2_rva = iat2_end_rva = rva;
		if(idesc_rva == 0 && ofsec.sec_atom == idata_atom)
			idesc_rva = idesc_end_rva = rva;
		if(rsrc_rva == 0 && ofsec.sec_atom == rsrc_atom)
			rsrc_rva = rsrc_end_rva = rva;
		section_object_index.Add(section.obj_sec.object);
//		section_obj_sec_index.Add(section.obj_sec);
		rva += section.size;
//		rfa += ofsec.size;
		if(ofsec.name_atom == idata_iat2_atom)
			iat2_end_rva = rva;
//		if(ofsec.name_atom == idata_idesc_atom || ofsec.name_atom == idata_inull_atom)
//			idesc_end_rva = rva;
		if(ofsec.sec_atom == idata_atom)
			idesc_end_rva = rva;
		if(ofsec.sec_atom == rsrc_atom)
			rsrc_end_rva = rva;
		const Section *nxt = (i + 1 >= section_map.GetCount() ? NULL : &section_map[i + 1]);
		while(prev_grp <= section.group)
			groups[prev_grp++].first_section = i;
	}
	while(prev_grp <= GRP_COUNT)
		groups[prev_grp++].first_section = section_map.GetCount();

	int rfa = 0, end_rva = 0;
	for(int i = 0; i <= GRP_COUNT; i++) {
		int first = groups[i].first_section;
		if(first >= section_map.GetCount()) {
			groups[i].rva = end_rva;
			groups[i].rfa = rfa;
			continue;
		}
		const Section& sec = section_map[first];
		int last = groups[i + 1].first_section - 1;
		const Section& lastsec = section_map[last];
//		const ObjectFile::Section& oflast = GetSection(lastsec.obj_sec);
		while(last >= first && section_map[last].udata)
			last--;
		const Section *rawsec = (last >= first ? &section_map[last] : NULL);
//		const ObjectFile::Section& ofsec = GetSection(sec.obj_sec);
		groups[i].rva = sec.rva;
		groups[i].rfa = rfa;
		groups[i].raw_size = rawsec ? rawsec->rva + GetSection(rawsec->obj_sec).size - sec.rva : 0;
		end_rva = (lastsec.rva + sec.size + image_align - 1) & -image_align;
		rfa = (rfa + groups[i].raw_size + file_align - 1) & -file_align;
	}

	for(int i = 0; i < section_map.GetCount(); i++) {
		Section& sec = section_map[i];
		Group& grp = groups[sec.group];
		if(!sec.udata)
			sec.rfa = sec.rva - grp.rva + grp.rfa;
	}

	if(dump_flags & DUMP_STAT)
		PutStdOut(NFormat("#collected sections = %d", section_map.GetCount()));
}

void LinkJob::RelocateGlobals()
{
	for(int i = 0; i < globals.GetCount(); i++) {
		Symbol& global = globals[i];
		if(global.used) {
			if(global.type != COFF_IMAGE_SYM_TYPE_BASE && global.obj_sec.section >= 0) {
				ObjSec os = global.obj_sec;
//				if(os.section == 0 && global.type < COFF_IMAGE_SYM_TYPE_SPECIAL)
//					os.object = 0; // comdat
				global.value += section_map[GetSection(os).sec_map_index].rva + code_rva;
			}
			global.relocated = true;
		}
	}
}

void LinkJob::RelocateExports()
{
	ObjectFile& ofe = objects[OBJ_EXPORT];
	if(!ofe.used_any)
		return;

	ObjectFile::Section& dir = ofe.sections[OBJE_DIRECTORY];
	ObjectFile::Section& adr = ofe.sections[OBJE_ADDRESS_TABLE];
	ObjectFile::Section& ptr = ofe.sections[OBJE_NAME_POINTERS];
	ObjectFile::Section& ord = ofe.sections[OBJE_ORDINALS];
	ObjectFile::Section& nam = ofe.sections[OBJE_NAMES];
	ASSERT(dir.sec_map_index >= 0 && adr.sec_map_index >= 0 && nam.sec_map_index >= 0);

	int nam_rva = section_map[nam.sec_map_index].rva + code_rva;
	int ptr_rva = section_map[ptr.sec_map_index].rva + code_rva;
	int adr_rva = section_map[adr.sec_map_index].rva + code_rva;

	COFF_IMAGE_EXPORT_DIRECTORY_TABLE expdir;
	memcpy(&expdir, dir.section_data.Begin(), sizeof(expdir));
	expdir.NameRVA = nam_rva;
	expdir.ExportAddressTableRVA = adr_rva;
	if(ptr.sec_map_index >= 0) {
		ASSERT(ord.sec_map_index >= 0);
		expdir.NamePointerTableRVA = ptr_rva;
		expdir.OrdinalTableRVA = section_map[ord.sec_map_index].rva + code_rva;
	}
	memcpy(dir.section_data.Begin(), &expdir, sizeof(expdir));

	for(int i = 0; i < ptr.size; i += 4) {
		byte *p = ptr.section_data.GetIter(i);
		PokeIL(p, PeekIL(p) + nam_rva);
	}

	for(int i = 0; i < exports.GetCount(); i++) {
		int f = collected_symbols.Get(exports.GetKey(i), -1);
		if(f >= 0) {
			Symbol& sym = globals[f];
			int offset = 4 * (exports[i].ordinal - expdir.OrdinalBase);
			PokeIL(adr.section_data.GetIter(offset), sym.value);
		}
	}
}

bool LinkJob::Less(const Section& a, const Section& b)
{
	int i;
	if((i = a.group - b.group) || (i = cmp(a.name, b.name)))
		return i < 0;
	if((i = a.obj_sec.object - b.obj_sec.object) || (i = a.obj_sec.section - b.obj_sec.section))
		return i < 0;
	return false;
}

void LinkJob::AddAuxCOFFSymbol(const COFF_IMAGE_SYMBOL& sym)
{
	coff_output_symbols.Add() = sym;
}

bool LinkJob::AddCOFFSymbol(String name, int object, const COFF_IMAGE_SYMBOL& sym)
{
	int value = sym.Value;
	int secno = sym.SectionNumber;
//	bool reloc_value = false;
	if(*name == '.' && sym.StorageClass != COFF_IMAGE_SYM_CLASS_FILE)
		return false; // probably also section entry
	if(sym.SectionNumber == 0 && sym.StorageClass == COFF_IMAGE_SYM_CLASS_EXTERNAL
	|| sym.StorageClass == COFF_IMAGE_SYM_CLASS_BLOCK || sym.StorageClass == COFF_IMAGE_SYM_CLASS_FUNCTION
	|| sym.StorageClass == COFF_IMAGE_SYM_CLASS_END_OF_STRUCT// || sym.StorageClass == COFF_IMAGE_SYM_CLASS_FILE
	|| sym.StorageClass == COFF_IMAGE_SYM_CLASS_REGISTER_PARAM || sym.StorageClass == COFF_IMAGE_SYM_CLASS_ARGUMENT
	|| sym.StorageClass == COFF_IMAGE_SYM_CLASS_REGISTER || sym.StorageClass == COFF_IMAGE_SYM_CLASS_AUTOMATIC
	|| sym.StorageClass == COFF_IMAGE_SYM_CLASS_END_OF_FUNCTION || sym.StorageClass == COFF_IMAGE_SYM_CLASS_SECTION) { // external symbol / anon COMDAT
		return false;
/*
		int global = collected_symbols.Get(Atomize(name), -1);
		if(global < 0)
			return false;
		value = globals[global].value;
		if(globals[global].obj_sec.section > 0)
			reloc_value = true;
*/
	}

	int appsec = sym.SectionNumber;
	if(sym.SectionNumber >= 0) {
		int sec_map_index = GetSection(ObjSec(object, sym.SectionNumber)).sec_map_index;
		if(sec_map_index < 0)
			return false;
		appsec = section_map[sec_map_index].app_section;
		value += section_map[sec_map_index].rva - section_map[section_name_map[appsec]].rva;
		appsec++;
//		reloc_value = true;
	}

	int offset = coff_output_symbols.GetCount() * sizeof(COFF_IMAGE_SYMBOL);
	COFF_IMAGE_SYMBOL& outsym = coff_output_symbols.Add();
	Zero(outsym);
	if(name.GetLength() <= 8)
		memcpy(outsym.N.ShortName, name, name.GetLength());
	else {
		outsym.N.Name.Long = coff_output_symbol_strings.GetLength() + 4;
		coff_output_symbol_strings.Cat(name, name.GetLength() + 1);
//		coff_reloc_offsets.Add(offset + __offsetof(COFF_IMAGE_SYMBOL, N.Name.Long));
	}
//	if(reloc_value)
//	{
//		value += image_base;
//		coff_reloc_offsets.Add(offset + __offsetof(COFF_IMAGE_SYMBOL, Value));
//	}
	outsym.Value = value;
	outsym.SectionNumber = appsec;
	outsym.StorageClass = sym.StorageClass;
	outsym.Type = sym.Type;
	outsym.NumberOfAuxSymbols = sym.NumberOfAuxSymbols;
	return true;
}

void LinkJob::AddWeakExternal(atom_t atom, int refatom)
{
	for(int f = weak_externals.Find(atom); f >= 0; f = weak_externals.FindNext(f))
		if(weak_externals[f] == refatom)
			return;
	weak_externals.Add(atom, refatom);
}

void LinkJob::AddGlobal(int atom, const Symbol& symbol)
{
	if(!symbol.external)
		obj_static_global_index.Add(Point(symbol.obj_sec.object, atom), globals.GetCount());
	globals.Add(atom, symbol);
	global_obj_sec_index.Add(symbol.obj_sec);
}

void LinkJob::PrepareImageHeader()
{
	output_image.Clear();
	String stub_data;
	if(!stub_filename.IsEmpty()) {
		stub_data = ::LoadFile(stub_filename);
		if(stub_data.IsVoid())
			PutStdOut(NFormat("%s: error loading stub file", stub_filename));
	}
	if(IsNull(stub_data))
		stub_data = String(std_stub, sizeof(std_stub));

	CatN(output_image, stub_data.GetLength(), stub_data);
	if(output_image.GetCount() < 0x40)
		output_image.SetCountR(0x40, 0);
	PokeIL(output_image.GetIter(0x3C), output_image.GetCount());
	CatN(output_image, 4, "PE\0\0");
	ifhdr_pos = CatN0(output_image, sizeof(COFF_IMAGE_FILE_HEADER));
	iohdr_pos = CatN0(output_image, sizeof(COFF_IMAGE_OPTIONAL_HEADER32));
	section_count = section_name_map.GetCount();
	if(has_stabs && debug_info) {
		stab_section = section_count++;
		stabstr_section = section_count++;
	}
	if(debug_info) {
//		has_symbols = true;
		symbol_section = section_count++;
	}
	if(!image_fixed) {
//		has_relocs = true;
		reloc_section = section_count++;
	}
	secmap_pos = CatN0(output_image, sizeof(COFF_IMAGE_SECTION_HEADER) * section_count);
//	impmap_pos = CatN0(output_image, sizeof(COFF_IMAGE_IMPORT_DESCRIPTOR) * (used_dll_objects.GetCount() + 1));
	header_size = output_image.GetCount();
	code_rfa = (header_size + file_align - 1) & -file_align;
	code_rva = (header_size + image_align - 1) & -image_align;
	output_image.SetCount(code_rfa + groups[GRP_COUNT].rfa, 0);
	memset(output_image.GetIter(code_rfa), 0x90, groups[GRP_DATA_BEGIN].rfa - groups[GRP_TEXT_BEGIN].rfa);
}

static bool SymTblLess(const COFF_IMAGE_SYMBOL& a, const COFF_IMAGE_SYMBOL& b)
{
	if(a.SectionNumber != b.SectionNumber) return a.SectionNumber < b.SectionNumber;
	return a.Value < b.Value;
}

void LinkJob::PrepareSymbolTable()
{
	LTIMING("LinkJob::PrepareSymbolTable");

/*
	for(int i = 0; i < section_name_map.GetCount(); i++) { // generate section beginning symbols
		COFF_IMAGE_SYMBOL secsym;
		Zero(secsym);
		strncpy((char *)secsym.N.ShortName, atoms[section_name_map.GetKey(i)], 8);
		secsym.SectionNumber = i + 1;
		secsym.Value = 0;
		secsym.StorageClass = COFF_IMAGE_SYM_CLASS_EXTERNAL;
		coff_output_symbols.Add(secsym);
	}
*/

//	Sort(coff_output_symbols, &SymTblLess);
	int rawsize = coff_output_symbols.GetCount() * sizeof(COFF_IMAGE_SYMBOL)
		+ 4 + coff_output_symbol_strings.GetLength();
	int imagesize = (rawsize + image_align - 1) & -image_align;
	int filesize = (rawsize + file_align - 1) & -file_align;
	for(int g = GRP_SYMBOLS; ++g <= GRP_COUNT;) {
		groups[g].rva += imagesize;
		groups[g].rfa += filesize;
	}
//	for(int g = 0; g < coff_output_symbols.GetCount(); g += 1 + coff_output_symbols[g].NumberOfAuxSymbols)
//		if(coff_output_symbols[g].N.Name.Short == 0)
//			coff_output_symbols[g].N.Name.Long += groups[GRP_SYMBOLS].rva + code_rva + image_base;
}

#ifdef CPU_LE // && CPU_32BIT
	#define GET_STAB(dest, ptr) dest = (STAB_INFO *)(ptr);
#else
	#define GET_STAB(dest, ptr) \
		dest->strdx = PeekIL(ptr + STAB_STRDXOFF); \
		dest->type = ptr[STAB_TYPEOFF]; \
		dest->other = ptr[STAB_OTHEROFF]; \
		dest->desc = PeekIW(ptr + STAB_DESCOFF); \
		dest->value = PeekIL(ptr + STAB_VALUEOFF);
#endif

void LinkJob::PrepareStabBlocks()
{
	LTIMING("LinkJob::PrepareStabBlocks");

	int count_stabs = output_stab_info.GetCount() / STAB_STABSIZE;
	if(dump_flags & DUMP_STABS) {
		STAB_INFO aux_stab, *stab = &aux_stab;
		for(int i = 0; i < count_stabs; i++) {
			GET_STAB(stab, output_stab_info.GetIter(i * STAB_STABSIZE));
			String desc;
			desc << NFormat("[%d]: type %02x, desc %04x, value %08x, %s",
				i, stab->type, stab->desc, stab->value, output_stab_strings.GetIter(stab->strdx));
			puts(desc);
		}
		puts(NFormat("Total %d stabs, .stab = %d B, .stabstr = %d B",
			count_stabs, output_stab_info.GetCount(), output_stab_strings.GetCount()));
	}
/*
	for(int f = 0; f < output_stab_fixup_pos.GetCount(); f++) {
		int sec_index = GetSection(output_stab_fixups[f]).sec_map_index;
		if(sec_index >= 0) {
			int pos = output_stab_fixup_pos[f];
			byte *p = output_stab_info.GetIter(STAB_STABSIZE * pos + STAB_VALOFF);
			PokeIL(p, PeekIL(p) + section_map[sec_index].rva + code_rva + image_base);
			highlow_fixup_rva.Add(pos * STAB_STABSIZE + STAB_VALOFF + groups[GRP_STAB].rva + code_rva);
		}
	}
*/
	PokeIW(output_stab_info.GetIter(STAB_DESCOFF), count_stabs);
	PokeIL(output_stab_info.GetIter(STAB_VALOFF), output_stab_strings.GetCount());

	groups[GRP_STABSTR].rva = groups[GRP_STAB].rva + ((output_stab_info.GetCount() + image_align - 1) & -image_align);
	groups[GRP_STABSTR].rfa = groups[GRP_STAB].rfa + ((output_stab_info.GetCount() + file_align - 1) & -file_align);
	groups[GRP_COUNT].rva = groups[GRP_SYMBOLS].rva = groups[GRP_STABSTR].rva + ((output_stab_strings.GetCount() + image_align - 1) & -image_align);
	groups[GRP_COUNT].rfa = groups[GRP_SYMBOLS].rfa = groups[GRP_STABSTR].rfa + ((output_stab_strings.GetCount() + file_align - 1) & -file_align);
}

void LinkJob::PrepareFixupBlocks()
{
	Sort(highlow_fixup_rva);
	int first = -1;
	Vector<byte> block;
	for(int i = 0; i < highlow_fixup_rva.GetCount(); i++) {
		int fp = highlow_fixup_rva[i];
		ASSERT(i == 0 || fp > highlow_fixup_rva[i - 1]); // duplicate relocations
		if(block.IsEmpty() || ((fp ^ first) & -4096)) {
			if(!block.IsEmpty()) {
				ASSERT(first >= 0);
				if(block.GetCount() & 2) {
					block.Add(0);
					block.Add(0);
				}
				int o = CatN(output_fixups, 8, NULL);
				PokeIL(&output_fixups[o], first);
				PokeIL(&output_fixups[o + 4], 8 + block.GetCount());
				output_fixups.AppendPick(block);
				block.Clear();
			}
			first = fp & -4096;
		}
		fp -= first;
		block.Add(fp);
		block.Add((fp >> 8) + 16 * COFF_IMAGE_REL_BASED_HIGHLOW);
	}
	if(!block.IsEmpty()) {
		ASSERT(first >= 0);
		if(block.GetCount() & 2) {
			block.Add(0);
			block.Add(0);
		}
		int o = CatN(output_fixups, 8, NULL);
		PokeIL(&output_fixups[o], first);
		PokeIL(&output_fixups[o + 4], 8 + block.GetCount());
		output_fixups.AppendPick(block);
	}
}

void LinkJob::PrepareImageInfo()
{
	CheckUnresolved();
	if(debug_info && stab_section >= 0)
		PrepareStabBlocks();
//	if(has_symbols)
	if(symbol_section >= 0)
		PrepareSymbolTable();
	if(!image_fixed)
		PrepareFixupBlocks();

	if(dump_flags & DUMP_SECTIONS) {
		PutStdOut(NFormat("Sections (%d total, %d parts):", section_name_map.GetCount(), section_map.GetCount()));
		for(int i = 0; i < section_name_map.GetCount(); i++) {
			const Section& sec = section_map[section_name_map[i]];
			int last_index = (i + 1 < section_name_map.GetCount()
				? section_name_map[i + 1] : section_map.GetCount()) - 1;
			const Section& last = section_map[last_index];
			const ObjectFile::Section& oflast = GetSection(last.obj_sec);
			PutStdOut(NFormat("%10<s RVA %08x RFA %08x LEN %08x",
				atoms[section_name_map.GetKey(i)], sec.rva, sec.rfa,
				last.rva - sec.rva + oflast.size));
		}
		PutStdOut(NFormat("Section groups (%d total):", GRP_COUNT));
		for(int i = 0; i <= GRP_COUNT; i++)
			PutStdOut(NFormat("[%d] RVA: %08x RFA %08x RAWSIZE %08x", i, groups[i].rva, groups[i].rfa, groups[i].raw_size));
	}

	if(dump_flags & DUMP_SECTIONS_ALL) {
		PutStdOut("Section map:");
		for(int i = 0; i < section_map.GetCount(); i++) {
			const Section& sec = section_map[i];
			const ObjectFile& of = objects[sec.obj_sec.object];
			const ObjectFile::Section& ofsec = of.sections[sec.obj_sec.section];
			PutStdOut(NFormat("RVA %08x, RFA %08x (%08x): %10<s %s: %s", sec.rva, sec.rfa,
				ofsec.size, atoms[ofsec.sec_atom], atoms[ofsec.name_atom], FormatSection(sec.obj_sec)));
			if(dump_flags & DUMP_SEC_DEFINES)
				for(int s = global_obj_sec_index.Find(sec.obj_sec); s >= 0; s = global_obj_sec_index.FindNext(s))
					if(globals[s].type != COFF_IMAGE_SYM_TYPE_SECTION)
						PutStdOut(NFormat("\tdefines: %s", atoms[globals.GetKey(s)]));
		}
//		for(i = 0; i < section_name_map.GetCount(); i++)
///			PutStdOut(NFormat("%s
	}

	int i;
	for(i = 0; i < section_name_map.GetCount(); i++) {
		COFF_IMAGE_SECTION_HEADER sechdr;
		Zero(sechdr);
		String name = atoms[section_name_map.GetKey(i)];
		memcpy(sechdr.Name, name, min(name.GetLength(), 8));
		int first_part = section_name_map[i];
		Section& first = section_map[first_part];
		int last_part;
		if(i + 1 < section_name_map.GetCount())
			last_part = section_name_map[i + 1] - 1;
		else
			last_part = section_map.GetCount() - 1;
		const Section& last = section_map[last_part];
		sechdr.Misc.VirtualSize = last.rva - first.rva + GetSection(last.obj_sec).size;
		sechdr.VirtualAddress = code_rva + first.rva;
		sechdr.SizeOfRawData = 0;
		for(; last_part >= first_part; last_part--) {
			const Section& chk = section_map[last_part];
			if(!chk.udata) {
				const ObjectFile::Section& ofsec = GetSection(chk.obj_sec);
				sechdr.SizeOfRawData = chk.rva - first.rva + ofsec.size;
				break;
			}
		}
		sechdr.SizeOfRawData = (sechdr.SizeOfRawData + file_align - 1) & -file_align;
		sechdr.PointerToRawData = (sechdr.SizeOfRawData ? code_rfa + first.rfa : 0);
		sechdr.PointerToRelocations = 0;
		sechdr.PointerToLinenumbers = 0;
		sechdr.Characteristics = GetSection(first.obj_sec).flags
			& (COFF_IMAGE_SCN_CNT_CODE | COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_CNT_UNINITIALIZED_DATA
				| COFF_IMAGE_SCN_MEM_EXECUTE | COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_MEM_WRITE
				| COFF_IMAGE_SCN_MEM_SHARED | COFF_IMAGE_SCN_MEM_DISCARDABLE | COFF_IMAGE_SCN_MEM_NOT_CACHED
				| COFF_IMAGE_SCN_MEM_NOT_PAGED);
		memcpy(&output_image[secmap_pos + (int)sizeof(COFF_IMAGE_SECTION_HEADER) * i], &sechdr, sizeof(sechdr));
	}

	enum {
//		STAB_SEC_STYLE = COFF_IMAGE_SCN_CNT_INITIALIZED_DATA | COFF_IMAGE_SCN_MEM_READ
//			| COFF_IMAGE_SCN_MEM_SHARED | COFF_IMAGE_SCN_MEM_DISCARDABLE
		STAB_SEC_STYLE = COFF_IMAGE_SCN_MEM_DISCARDABLE | COFF_IMAGE_SCN_MEM_READ
		| COFF_IMAGE_SCN_TYPE_NOLOAD | COFF_IMAGE_SCN_LNK_REMOVE
	};

	if(stab_section >= 0) {
		COFF_IMAGE_SECTION_HEADER stab;
		Zero(stab);
		strcpy((char *)stab.Name, ".stab");
		stab.Misc.VirtualSize = output_stab_info.GetCount();
		stab.SizeOfRawData = (stab.Misc.VirtualSize + file_align - 1) & -file_align;
		stab.VirtualAddress = groups[GRP_STAB].rva + code_rva;
		stab.PointerToRawData = groups[GRP_STAB].rfa + code_rfa;
		stab.Characteristics = STAB_SEC_STYLE;
		memcpy(&output_image[secmap_pos + (int)sizeof(COFF_IMAGE_SECTION_HEADER) * stab_section],
			&stab, sizeof(stab));
	}

	if(stabstr_section >= 0) {
		COFF_IMAGE_SECTION_HEADER stabstr;
		Zero(stabstr);
		memcpy((char *)stabstr.Name, ".stabstr", 8);
		stabstr.Misc.VirtualSize = output_stab_strings.GetCount();
		stabstr.SizeOfRawData = (stabstr.Misc.VirtualSize + file_align - 1) & -file_align;
//		stabstr.Misc.VirtualSize = stabstr.SizeOfRawData = groups[GRP_COUNT].rva - groups[GRP_STABSTR].rva;
		stabstr.VirtualAddress = groups[GRP_STABSTR].rva  + code_rva;
		stabstr.PointerToRawData = groups[GRP_STABSTR].rfa + code_rfa;
		stabstr.Characteristics = STAB_SEC_STYLE;
		memcpy(&output_image[secmap_pos + (int)sizeof(COFF_IMAGE_SECTION_HEADER) * stabstr_section],
			&stabstr, sizeof(stabstr));
	}

	if(symbol_section >= 0) {
		COFF_IMAGE_SECTION_HEADER sym;
		Zero(sym);
		strcpy((char *)sym.Name, ".symbol");
		sym.Misc.VirtualSize = coff_output_symbols.GetCount() * sizeof(COFF_IMAGE_SYMBOL)
			+ 4 + coff_output_symbol_strings.GetLength();
		sym.SizeOfRawData = (sym.Misc.VirtualSize + file_align - 1) & -file_align;
		sym.VirtualAddress = groups[GRP_SYMBOLS].rva + code_rva;
		sym.PointerToRawData = groups[GRP_SYMBOLS].rfa + code_rfa;
		sym.Characteristics = COFF_IMAGE_SCN_LNK_REMOVE | COFF_IMAGE_SCN_MEM_DISCARDABLE
			| COFF_IMAGE_SCN_MEM_READ | COFF_IMAGE_SCN_TYPE_NOLOAD;
		memcpy(&output_image[secmap_pos + (int)sizeof(COFF_IMAGE_SECTION_HEADER) * symbol_section],
			&sym, sizeof(sym));
	}

	output_fixup_size = 0;
	if(reloc_section >= 0) { // fill in relocation segment header
		COFF_IMAGE_SECTION_HEADER reloc;
		Zero(reloc);
		strcpy((char *)reloc.Name, ".reloc");
		reloc.SizeOfRawData = reloc.Misc.VirtualSize = output_fixups.GetCount();
		output_fixup_size = (reloc.Misc.VirtualSize + file_align - 1) & -file_align;
		reloc.VirtualAddress = groups[GRP_COUNT].rva + code_rva;
		reloc.PointerToRawData = groups[GRP_COUNT].rfa + code_rfa;
		reloc.Characteristics = COFF_IMAGE_SCN_MEM_DISCARDABLE | COFF_IMAGE_SCN_MEM_READ;
		memcpy(&output_image[secmap_pos + (int)sizeof(COFF_IMAGE_SECTION_HEADER) * reloc_section],
			&reloc, sizeof(reloc));
	}

/*
	for(i = 0; i < used_dll_objects.GetCount(); i++) {
		const ObjectFile& of = objects[used_dll_objects[i]];
		COFF_IMAGE_IMPORT_DESCRIPTOR idesc;
		Zero(idesc);
		idesc.OriginalFirstThunk = section_map[of.sections[ObjectFile::SEC_DLL_IMPORTS].sec_map_index].rva;
		idesc.ForwarderChain = -1;
		idesc.Name = section_map[of.sections[ObjectFile::SEC_DLL_NAMES].sec_map_index].rva;
		idesc.FirstThunk = section_map[of.sections[ObjectFile::SEC_DLL_BOUND].sec_map_index].rva;
		memcpy(&output_image[impmap_pos + i * (int)sizeof(idesc)], &idesc, sizeof(idesc));
	}
*/
	COFF_IMAGE_OPTIONAL_HEADER32 opthdr;
	Zero(opthdr);
	opthdr.Magic = 0x10b; // PE32; PE32+ = 0x20b
	opthdr.MajorLinkerVersion = ULD_VERSION_MAJOR * 10 + ULD_VERSION_MINOR;
	opthdr.MinorLinkerVersion = ULD_VERSION_RELEASE;
	opthdr.SizeOfCode = groups[GRP_DATA_BEGIN].rva;
	opthdr.SizeOfInitializedData = groups[GRP_BSS_BEGIN].rva - groups[GRP_DATA_BEGIN].rva;
	opthdr.SizeOfUninitializedData = groups[GRP_BSS_END].rva - groups[GRP_BSS_BEGIN].rva;
	int entry = collected_symbols[0];
	if(entry >= 0) {
		const Symbol& sym = globals[entry];
		if(sym.obj_sec.section < 0 && !image_fixed)
			PutStdOut(NFormat("%s: absolute entry point in non-fixed application, application may not work", atoms[globals.GetKey(entry)]));
		opthdr.AddressOfEntryPoint = sym.value;
	}
	opthdr.BaseOfCode = code_rva;
	opthdr.BaseOfData = code_rva + groups[GRP_DATA_BEGIN].rva;

	opthdr.ImageBase = image_base;
	opthdr.SectionAlignment = image_align;
	opthdr.FileAlignment = file_align;
	opthdr.MajorOperatingSystemVersion = 4;
	opthdr.MinorOperatingSystemVersion = 0;
	opthdr.MajorImageVersion = major_version;
	opthdr.MinorImageVersion = minor_version;
	opthdr.MajorSubsystemVersion = major_subsystem_version;
	opthdr.MinorSubsystemVersion = minor_subsystem_version;
	opthdr.Win32VersionValue = 0;
//	opthdr.SizeOfImage = (code_rva + groups[GRP_COUNT].rva + output_fixups.GetCount() + file_align - 1) & -file_align;
	opthdr.SizeOfImage = code_rva + groups[GRP_COUNT].rva + ((output_fixup_size + image_align - 1) & -image_align);
	opthdr.SizeOfHeaders = code_rfa;
	opthdr.CheckSum = 0;
	opthdr.Subsystem = subsystem;
	opthdr.DllCharacteristics;
	opthdr.SizeOfStackReserve = stack_reserve;
	opthdr.SizeOfStackCommit = stack_commit;
	opthdr.SizeOfHeapReserve = heap_reserve;
	opthdr.SizeOfHeapCommit = heap_commit;
	opthdr.LoaderFlags = 0;
	opthdr.NumberOfRvaAndSizes = __countof(opthdr.DataDirectory);
	opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = output_fixups.GetCount();
	if(!output_fixups.IsEmpty())
		opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = groups[GRP_COUNT].rva + code_rva;
	if(opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_EXPORT].Size = groups[GRP_EDATA].raw_size)
		opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress = groups[GRP_EDATA].rva + code_rva;
	if(opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_IMPORT].Size = idesc_end_rva - idesc_rva)
		opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = idesc_rva + code_rva;
	if(opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_IAT].Size = iat2_end_rva - iat2_rva)
		opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress = iat2_rva + code_rva;
	if(opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = rsrc_end_rva - rsrc_rva)
		opthdr.DataDirectory[COFF_IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = code_rva + rsrc_rva;
	memcpy(&output_image[iohdr_pos], &opthdr, sizeof(opthdr));

	COFF_IMAGE_FILE_HEADER ifhdr;
	Zero(ifhdr);
	ifhdr.Machine = machine;
	ifhdr.NumberOfSections = section_count;
	ifhdr.TimeDateStamp = timestamp = time(NULL);
	ifhdr.PointerToSymbolTable = 0;
	ifhdr.NumberOfSymbols = 0;
	if(symbol_section >= 0) {
		ifhdr.PointerToSymbolTable = code_rfa + groups[GRP_SYMBOLS].rfa;
		ifhdr.NumberOfSymbols = coff_output_symbols.GetCount();
	}
	ifhdr.SizeOfOptionalHeader = sizeof(opthdr); //!! temporary
	ifhdr.Characteristics = COFF_IMAGE_FILE_EXECUTABLE_IMAGE
		| COFF_IMAGE_FILE_32BIT_MACHINE
//		| COFF_IMAGE_FILE_BYTES_REVERSED_LO
		| COFF_IMAGE_FILE_LINE_NUMS_STRIPPED
		| (debug_info ? 0 : COFF_IMAGE_FILE_LOCAL_SYMS_STRIPPED)
		| (debug_info ? 0 : COFF_IMAGE_FILE_DEBUG_STRIPPED)
		| (image_fixed ? COFF_IMAGE_FILE_RELOCS_STRIPPED : 0)
		| (make_dll ? COFF_IMAGE_FILE_DLL : 0)
		// COFF_IMAGE_FILE_AGGRESSIVE_WS_TRIM
		// COFF_IMAGE_FILE_LARGE_ADDRESS_AWARE
		// COFF_IMAGE_FILE_16BIT_MACHINE
		// COFF_IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP
		// COFF_IMAGE_FILE_SYSTEM
		// COFF_IMAGE_FILE_DLL
		// COFF_IMAGE_FILE_UP_SYSTEM_ONLY
		// COFF_IMAGE_FILE_BYTES_REVERSED_HI
		;
	memcpy(&output_image[ifhdr_pos], &ifhdr, sizeof(ifhdr));

	if(dump_flags & DUMP_STAT) {
		PutStdOut(NFormat("Preferred image base: %08x", image_base));
		PutStdOut(NFormat("Entry point RVA:      %08x", (int)opthdr.AddressOfEntryPoint));
	}
}

void LinkJob::WriteImageFile()
{
	outputfile = AppendExt(outputfile, ".exe");
	FileOut fo;
	if(!fo.Open(outputfile))
		throw Exc(NFormat("%s: error creating file", outputfile));
	if(verbose)
		PutStdOut(NFormat("%s: writing output file (%d bytes)",
			outputfile, groups[GRP_COUNT].rfa + code_rfa + output_fixups.GetCount()));
	fo.Put(output_image.Begin(), output_image.GetCount());
	if(stab_section >= 0) {
		fo.Put(0, int(groups[GRP_STAB].rfa + code_rfa - fo.GetPos()));
		fo.Put(output_stab_info.Begin(), output_stab_info.GetCount());
	}
	if(stabstr_section >= 0) {
		fo.Put(0, int(groups[GRP_STABSTR].rfa + code_rfa - fo.GetPos()));
		fo.Put(output_stab_strings.Begin(), output_stab_strings.GetCount());
	}
	if(symbol_section >= 0) {
		fo.Put(0, int(groups[GRP_SYMBOLS].rfa + code_rfa - fo.GetPos()));
		fo.Put(coff_output_symbols.Begin(), coff_output_symbols.GetCount() * sizeof(COFF_IMAGE_SYMBOL));
		fo.PutIL(coff_output_symbol_strings.GetLength() + 4);
		fo.Put(coff_output_symbol_strings.Begin(), coff_output_symbol_strings.GetLength());
	}
	fo.Put(0, int(code_rfa + groups[GRP_COUNT].rfa - fo.GetPos()));
	if(!image_fixed)
		fo.Put(output_fixups.Begin(), output_fixups.GetCount());
//	fo.Put(0, int(opthdr.SizeOfImage - fo.GetPos()));
	fo.Close();
	if(fo.IsError()) {
		FileDelete(outputfile);
		throw Exc(NFormat("%s: error writing output file", outputfile));
	}

	if(write_mapfile)
		WriteMapFile();
}

/*
static const char cachefile_header[32] = "ULD Link Cache File v. 1\r\n\x1A\0";
enum
{
	CACHE_TAG_SIZE    = 32,
	CACHE_DIROFF_SIZE = 4,
	CACHE_DATA_POS    = CACHE_TAG_SIZE + CACHE_DIROFF_SIZE,
	CACHE_DIR_HEAD    = 0x02468ACE,
	CACHE_DIR_TAIL    = 0x13579BDF,
	CACHE_MAX_SIZE    = 500000000,
};
*/

/*
void LinkJob::OpenCacheFile()
{
	cache_dirty = false;
	if(!cache_objects || IsNull(cache_file_name))
		return;
	if(cache_file.Open(cache_file_name, FileStream::READWRITE)) {
		char buffer[CACHE_TAG_SIZE + CACHE_DIROFF_SIZE];
		if(!cache_file.GetAll(buffer, sizeof(buffer)) || memcmp(buffer, cachefile_header, CACHE_TAG_SIZE))
			cache_file.SetError();
		else {
			int offset = PeekIL(buffer + CACHE_TAG_SIZE); ASSERT(CACHE_DIROFF_SIZE == 4);
			if(offset < CACHE_DATA_POS || offset >= (int)cache_file.GetSize() - 8)
				cache_file.SetError();
			else {
				cache_file.Seek(offset);
				if(cache_file.GetIL() != CACHE_DIR_HEAD)
					cache_file.SetError();
				else {
					cache_file.SetLoading();
					cache_file % object_cache;
					int tail = cache_file.GetIL();
					if(tail != CACHE_DIR_TAIL || cache_file.GetLeft() != 0)
						cache_file.SetError();
				}
			}
		}
		if(cache_file.IsError()) {
			PutStdOut(NFormat("Rewriting cache file '%s'", cache_file_name));
			object_cache.Clear();
			cache_file.ClearError();
			cache_file.Seek(0);
			cache_file.SetSize(0);
		}
		else
			PutStdOut(NFormat("Open cache file '%s', %d objects", cache_file_name, object_cache.GetCount()));
		cache_name_index.Clear();
		cache_library_index.Clear();
		for(int i = 0; i < object_cache.GetCount(); i++) {
			const Cache& obj = object_cache[i];
			cache_name_index.Add(String().Cat() << obj.library << ':' << obj.file);
			cache_library_index.Add(obj.library);
		}
	}
}
*/

/*
void LinkJob::SaveCacheFile()
{
	if(!cache_objects)
		return;
	if(cache_dirty) {
		if(!cache_file.IsError()) {
			int first_pos = CACHE_DATA_POS, used_size = 0;
			for(int i = 0; i < object_cache.GetCount(); i++) {
				used_size += object_cache[i].size;
				first_pos = object_cache[i].offset + object_cache[i].size;
			}
			if(cache_file.GetSize() >= 2u * used_size)
				cache_file.SetError();
			else {
				cache_file.Seek(first_pos);
				cache_file.PutIL(CACHE_DIR_HEAD);
				cache_file.SetStoring();
				cache_file % object_cache;
				cache_file.PutIL(CACHE_DIR_TAIL);
				cache_file.SetSize(cache_file.GetSize());
				if(!cache_file.IsError()) {
					cache_file.Seek(CACHE_TAG_SIZE);
					cache_file.PutIL(first_pos);
				}
			}
		}
	}
	cache_file.Close();
	if(cache_file.IsError())
		FileDelete(cache_file_name);
}
*/

void LinkJob::WriteImageSections()
{
	LTIMING("LinkJob::WriteImageSections");

//	OpenCacheFile();

	for(int i = 0; i < objects.GetCount(); i++) {
		ObjectFile& of = objects[i];
		if(!of.used_any)
			continue;
		if(of.dll_atom)
			RelocateImport(of);
		else if(of.filetype == ObjectFile::INTERNAL)
			RelocateInternal(of);
		else {
			const byte *begin = 0;
/*
			Buffer<byte> cachebuf;
			String cache_key;
			if(cache_objects)
				cache_key << of.library_file << ':' << of.object_file;
			int f;
			if(cache_objects && !cache_file.IsError() && (f = cache_name_index.Find(cache_key)) >= 0) {
				Cache& cache = object_cache[f];
				ASSERT(cache.library == of.library_file);
				ASSERT(cache.file == of.object_file);
				if(cache.time == of.file_time && cache.size == of.object_size) {
					LTIMING("reading cache data");
					cachebuf.Alloc(cache.size);
					cache_file.Seek(cache.offset);
					if(!cache_file.GetAll(cachebuf, cache.size))
						cache_file.SetError();
					if(!cache_file.IsError())
						begin = cachebuf;
				}
				else {
					Vector<int> to_remove;
					if(IsNull(of.library_file))
						to_remove.Add(f);
					else
						for(int r = cache_library_index.Find(of.library_file); r >= 0; r = cache_library_index.FindNext(r))
							to_remove.Add(r);
					while(!to_remove.IsEmpty()) {
						int i = to_remove.Pop();
						cache_name_index.Remove(i);
						cache_library_index.Remove(i);
						object_cache.Remove(i);
					}
				}
			}
*/
			if(!begin && !of.object_data.IsEmpty())
				begin = of.object_data;
			if(!begin) {
				String ofile = Nvl(of.library_file, of.object_file);
				FileMapping& mapping = GetMapping(ofile);
				if(!mapping.IsOpen())
					throw Exc(NFormat("%s: error opening file", ofile));
				begin = mapping.Begin() + of.library_offset;
/*
				if(cache_objects && !cache_file.IsError()) {
					int first_pos = 36;
					int i = 0;
					while(i < object_cache.GetCount() && object_cache[i].offset - first_pos < of.object_size) {
						first_pos = object_cache[i].offset + object_cache[i].size;
						i++;
					}
					if(i && i >= object_cache.GetCount())
						first_pos += object_cache.Top().size >> 3;
					if(first_pos + of.object_size < CACHE_MAX_SIZE) {
						if(!cache_dirty) {
							if(cache_file.GetSize() < CACHE_TAG_SIZE) {
								cache_file.Seek(0);
								cache_file.Put(cachefile_header, CACHE_TAG_SIZE);
							}
							cache_file.Seek(CACHE_TAG_SIZE);
							cache_file.PutIL(0); ASSERT(CACHE_DIROFF_SIZE == 4);
							cache_dirty = true;
						}
						if((unsigned)first_pos > cache_file.GetSize()) {
							cache_file.SeekEnd();
							cache_file.Put('\0', int(first_pos - cache_file.GetPos()));
						}
						else
							cache_file.Seek(first_pos);
						cache_file.Put(begin, of.object_size);
						Cache& new_item = object_cache.Insert(i);
						new_item.library = of.library_file;
						new_item.file = of.object_file;
						new_item.time = of.file_time;
						new_item.offset = first_pos;
						new_item.size = of.object_size;
						cache_name_index.Insert(i, cache_key);
						cache_library_index.Insert(i, new_item.library);
					}
				}
*/
			}
			LTIMING("relocating objects");
			RelocateObject(of, begin);
			if(debug_info)
				RelocateStabs(of, begin);
		}
	}

//	SaveCacheFile();
}

void LinkJob::RelocateImport(const ObjectFile& of)
{
	const ObjectFile::Section& stubsec = of.sections[ObjectFile::SEC_DLL_STUBS];
	const ObjectFile::Section& impsec = of.sections[ObjectFile::SEC_DLL_IMPORTS];
	int impsec_rva = section_map[impsec.sec_map_index].rva + code_rva;
	const ObjectFile::Section& bimpsec = of.sections[ObjectFile::SEC_DLL_BOUND];
	int bimpsec_rva = section_map[bimpsec.sec_map_index].rva + code_rva;
	const ObjectFile::Section& namesec = of.sections[ObjectFile::SEC_DLL_NAMES];
	int namesec_rva = (namesec.sec_map_index >= 0 ? section_map[namesec.sec_map_index].rva + code_rva : 0);
	const ObjectFile::Section& descsec = of.sections[ObjectFile::SEC_DLL_DESCRIPTOR];
	int descsec_rva = section_map[descsec.sec_map_index].rva + code_rva;
	for(int s = section_object_index.Find(of.index); s >= 0; s = section_object_index.FindNext(s)) {
		const Section& sec = section_map[s];
		ASSERT(sec.obj_sec.object == of.index);
		int sec_rva = code_rva + sec.rva;
		byte *sbegin = &output_image[code_rfa + sec.rfa];
		const ObjectFile::Section& ofsec = of.sections[sec.obj_sec.section];
		memcpy(sbegin, ofsec.section_data.Begin(), ofsec.section_data.GetCount());
		if(sec.obj_sec.section == ObjectFile::SEC_DLL_STUBS) {
			ASSERT(ofsec.size == imp_stub_size * of.used_stubs.GetCount());
			for(int i = 0; i < of.used_stubs.GetCount(); i++, sbegin += imp_stub_size) {
				int imp = (1 + of.used_imports.Find(of.used_stubs[i])) * imp_entry_size;
				switch(machine) {
					case COFF_IMAGE_FILE_MACHINE_I386: {
						sbegin[0] = 0xFF; // JMP dword ptr [12345678]
						sbegin[1] = 0x25;
						ASSERT(imp >= 0);
						PokeIL(sbegin + 2, image_base + bimpsec_rva + imp);
						highlow_fixup_rva.Add(sec_rva + 2 + i * imp_stub_size);
						break;
					}
					case COFF_IMAGE_FILE_MACHINE_ARM: {
						PokeIL(sbegin + 0, 0xE59FC000);
						PokeIL(sbegin + 4, 0xE59CF000);
						PokeIL(sbegin + 8, image_base + bimpsec_rva + imp);
						highlow_fixup_rva.Add(sec_rva + 8 + i * imp_stub_size);
						break;
					}
					default: {
						PutStdOut(NFormat("%s: stub generation for %s not implemented", AsString(of), COFFMachineName(machine)));
						return;
					}
				}
			}
		}
		else if(sec.obj_sec.section == ObjectFile::SEC_DLL_IMPORTS || sec.obj_sec.section == ObjectFile::SEC_DLL_BOUND) {
			for(int i = 0; i < of.used_imports.GetCount(); i++) {
				sbegin += imp_entry_size;
				int imptr = PeekIL(sbegin);
				if(!(imptr & 0x80000000)) {
					imptr += namesec_rva;
					PokeIL(sbegin, imptr);
//					highlow_fixup_rva.Add(sec_rva + IMP_ENTRY_SIZE * (i + 1));
				}
			}
		}
		else if(sec.obj_sec.section == ObjectFile::SEC_DLL_DESCRIPTOR) {
			COFF_IMAGE_IMPORT_DESCRIPTOR idesc;
			Zero(idesc);
			idesc.OriginalFirstThunk = impsec_rva + imp_entry_size;
			idesc.ForwarderChain = 0;
			idesc.Name = namesec_rva;
			idesc.FirstThunk = bimpsec_rva + imp_entry_size;
			memcpy(sbegin, &idesc, sizeof(idesc));
//			highlow_fixup_rva.Add(sec_rva + __offsetof(COFF_IMAGE_IMPORT_DESCRIPTOR, OriginalFirstThunk));
//			highlow_fixup_rva.Add(sec_rva + __offsetof(COFF_IMAGE_IMPORT_DESCRIPTOR, Name));
//			highlow_fixup_rva.Add(sec_rva + __offsetof(COFF_IMAGE_IMPORT_DESCRIPTOR, FirstThunk));
		}
	}
}

void LinkJob::RelocateInternal(const ObjectFile& of)
{
	LTIMING("LinkJob::RelocateInternal");

	for(int s = section_object_index.Find(of.index); s >= 0; s = section_object_index.FindNext(s)) {
		const Section& sec = section_map[s];
		if(sec.obj_sec.object == 0 && sec.obj_sec.section == 0)
			continue; // comdat
		ASSERT(sec.obj_sec.object == of.index);
		int destseg_rva = code_rva + sec.rva;
		byte *sbegin = &output_image[code_rfa + sec.rfa];
		const ObjectFile::Section& ofsec = GetSection(sec.obj_sec);
		if(/*ofsec.type == ObjectFile::SEC_INULL ||*/ ofsec.type == ObjectFile::SEC_ANON_COMDAT)
			continue;
		ASSERT(ofsec.type == ObjectFile::SEC_STD || ofsec.type == ObjectFile::SEC_RAW);
		int datalen = min(ofsec.section_data.GetCount(), ofsec.size);
		memcpy(sbegin, ofsec.section_data.Begin(), datalen);
	}
}

void LinkJob::RelocateObject(const ObjectFile& of, const byte *begin)
{
	LTIMING("LinkJob::RelocateObject");

	const COFF_IMAGE_FILE_HEADER *header = (const COFF_IMAGE_FILE_HEADER *)begin;
	const COFF_IMAGE_SECTION_HEADER *sechdr = (const COFF_IMAGE_SECTION_HEADER *)(begin
		+ sizeof(COFF_IMAGE_FILE_HEADER) + header->SizeOfOptionalHeader);
	const COFF_IMAGE_SYMBOL *symtbl = (const COFF_IMAGE_SYMBOL *)(begin + header->PointerToSymbolTable);
	const char *strtbl = (const char *)(symtbl + header->NumberOfSymbols);
	for(int s = section_object_index.Find(of.index); s >= 0; s = section_object_index.FindNext(s)) {
		const Section& sec = section_map[s];
		if(sec.obj_sec.object == 0 && sec.obj_sec.section == 0 || sec.udata)
			continue; // comdat
		ASSERT(sec.obj_sec.object == of.index);
		int destseg_rva = code_rva + sec.rva;
		byte *sbegin = &output_image[code_rfa + sec.rfa];
		const ObjectFile::Section& ofsec = GetSection(sec.obj_sec);
		ASSERT(ofsec.type == ObjectFile::SEC_STD);
		const COFF_IMAGE_SECTION_HEADER& secdata = sechdr[sec.obj_sec.section - 1];
		if(ofsec.offset)
			memcpy(sbegin, begin + ofsec.offset, ofsec.raw_size);
		const COFF_IMAGE_RELOCATION *relptr = (const COFF_IMAGE_RELOCATION *)(begin + secdata.PointerToRelocations);
		const COFF_IMAGE_RELOCATION *relend = relptr + secdata.NumberOfRelocations;
		for(; relptr < relend; relptr++) {
			int delta_rva = relptr->VirtualAddress - secdata.VirtualAddress;
			int target_rva = destseg_rva + delta_rva;
			byte *target = &output_image[(int)(code_rfa + sec.rfa + delta_rva)];
			int value = 0;
			ObjSec obj_sec = Null;
			const COFF_IMAGE_SYMBOL& relsym = symtbl[relptr->SymbolTableIndex];
			int ref_atom = NameAtom(symtbl[relptr->SymbolTableIndex], strtbl);
			if(relsym.SectionNumber < 0) {
				value = relsym.Value;
				obj_sec.section = relsym.SectionNumber;
			}
			else if(relsym.StorageClass == COFF_IMAGE_SYM_CLASS_EXTERNAL
			|| relsym.StorageClass == COFF_IMAGE_SYM_CLASS_WEAK_EXTERNAL
//			|| relsym.SectionNumber > 0 && of.sections[relsym.SectionNumber].flags & COFF_IMAGE_SCN_LNK_COMDAT
			) {
				int coll = collected_symbols.Find(ref_atom);
				if(coll < 0 || collected_symbols[coll] < 0) {
					if(relsym.StorageClass == COFF_IMAGE_SYM_CLASS_WEAK_EXTERNAL) {
						for(int f = weak_externals.Find(ref_atom); f >= 0; f = weak_externals.FindNext(f)) {
							int c = collected_symbols.Find(weak_externals[f]);
							if(c >= 0 && collected_symbols[c] >= 0) {
								coll = c;
								break;
							}
						}
					}
					unresolved.FindAdd(ref_atom);
					continue;
				}
				const Symbol& sym = globals[collected_symbols[coll]];
				if(!sym.relocated) {
					unresolved.FindAdd(ref_atom);
					continue;
				}
				value = sym.value;
				obj_sec = sym.obj_sec;
			}
			else if(relsym.SectionNumber > 0) {
				int sec_map_index = of.sections[relsym.SectionNumber].sec_map_index;
				if(sec_map_index >= 0) {
					obj_sec.object = of.index;
					obj_sec.section = relsym.SectionNumber;
					value = relsym.Value + section_map[sec_map_index].rva + code_rva;
				}
				else
					PutStdOut(NFormat("%s: invalid static relocation to %s (%d)",
						AsString(of), atoms[ref_atom], relsym.SectionNumber));
			}
			else
				PutStdOut(NFormat("%s: invalid relocation to %s (%d)",
					AsString(of), atoms[ref_atom], relsym.SectionNumber));
			switch(machine) {
				case COFF_IMAGE_FILE_MACHINE_I386: {
					switch(relptr->Type) {
						case COFF_IMAGE_REL_I386_ABSOLUTE: {
							break;
						}
			//			case COFF_IMAGE_REL_I386_DIR16:
			//			case COFF_IMAGE_REL_I386_REL16:
						case COFF_IMAGE_REL_I386_DIR32: {
							PokeIL(target, PeekIL(target) + value + (obj_sec.section >= 0 ? image_base : 0));
							if(obj_sec.section >= 0)
								highlow_fixup_rva.Add(target_rva);
							break;
						}

						case COFF_IMAGE_REL_I386_DIR32NB: {
							PokeIL(target, PeekIL(target) + value);
							break;
						}

			//			case COFF_IMAGE_REL_I386_SEG12:
						case COFF_IMAGE_REL_I386_SECTION:
						case COFF_IMAGE_REL_I386_SECREL: {
								int xsec = obj_sec.section >= 0 ? section_map[GetSection(obj_sec).sec_map_index].app_section : -1;
								if(relptr->Type == COFF_IMAGE_REL_I386_SECTION)
									PokeIW(target, PeekIW(target) + xsec);
								else
									PokeIL(target, PeekIL(target) + code_rva + sec.rva - (xsec >= 0 ? section_map[section_name_map[xsec]].rva : 0));
							}
							break;

						case COFF_IMAGE_REL_I386_REL32: {
							PokeIL(target, PeekIL(target) + value - target_rva - 4);
							break;
						}

						default: {
							PutStdOut(NFormat("%s: invalid i386 relocation type %d", AsString(of), relptr->Type));
							break;
						}
					}
					break;
				}
				case COFF_IMAGE_FILE_MACHINE_ARM: {
					switch(relptr->Type) {
						case COFF_IMAGE_REL_ARM_ABSOLUTE: {
							break;
						}
						case COFF_IMAGE_REL_ARM_ADDR32: {
							PokeIL(target, PeekIL(target) + value + (obj_sec.section >= 0 ? image_base : 0));
							if(obj_sec.section >= 0)
								highlow_fixup_rva.Add(target_rva);
							break;
						}
						case COFF_IMAGE_REL_ARM_ADDR32NB: {
							PokeIL(target, PeekIL(target) + value);
							break;
						}
						case COFF_IMAGE_REL_ARM_BRANCH24: {
							int displ = value - target_rva - 8;
							int trunc = (displ & (1 << 25)) ? (displ | (-1 << 26)) : (displ & ((1 << 26) - 1)) & -4;
							if(displ & 3)
								PutStdOut(NFormat("%s: misaligned 24-bit relocation (delta = %08x)", AsString(of), displ));
							else if(displ != trunc)
								PutStdOut(NFormat("%s: 24-bit relocation out of range (delta = %08x)", AsString(of), displ));
							else
								PokeIL(target, (PeekIL(target) & (-1 << 24)) | ((displ >> 2) & ((1 << 24) - 1)));
							break;
						}
						case COFF_IMAGE_REL_ARM_BRANCH11: {
							int displ = value - target_rva - 8;
							int trunc = (displ & (1 << 23)) ? (displ | (-1 << 24)) : (displ & ((1 << 24) - 1)) & -4;
							if(displ & 3)
								PutStdOut(NFormat("%s: misaligned 22-bit relocation (delta = %08x)", AsString(of), displ));
							else if(displ != trunc)
								PutStdOut(NFormat("%s: 22-bit relocation out of range (delta = %08x)", AsString(of), displ));
							else {
								PokeIW(target + 0, (PeekIW(target + 0) & (-1 << 11)) | ((displ >> 2) & ((1 << 11) - 1)));
								PokeIW(target + 2, (PeekIW(target + 2) & (-1 << 11)) | ((displ >> 13) & ((1 << 11) - 1)));
							}
							break;
						}
						case COFF_IMAGE_REL_ARM_SECTION:
						case COFF_IMAGE_REL_ARM_SECREL: {
							int xsec = obj_sec.section >= 0 ? section_map[GetSection(obj_sec).sec_map_index].app_section : -1;
							if(relptr->Type == COFF_IMAGE_REL_ARM_SECTION)
								PokeIW(target, PeekIW(target) + xsec);
							else
								PokeIL(target, PeekIL(target) + code_rva + sec.rva - (xsec >= 0 ? section_map[section_name_map[xsec]].rva : 0));
							break;
						}
					default:
						PutStdOut(NFormat("%s: invalid ARM relocation type %d", AsString(of), relptr->Type));
						break;
					}
					break;
				}
				default: {
					PutStdOut(NFormat("%s: relocations for machine %04x not implemented", AsString(of), machine));
					break;
				}
			}
		}
	}

	if(debug_info) {
		const COFF_IMAGE_SYMBOL *symend = symtbl + header->NumberOfSymbols;
		for(const COFF_IMAGE_SYMBOL *symp = symtbl; symp < symend; symp++) {
			if(symp->StorageClass == COFF_IMAGE_SYM_CLASS_STATIC && symp->SectionNumber != 0
			&& symp->Value == 0 && symp->NumberOfAuxSymbols >= 1)
				symp += symp->NumberOfAuxSymbols; // section entry
			else if(AddCOFFSymbol(COFFSymbolName(*symp, strtbl), of.index, *symp))
				for(int naux = symp->NumberOfAuxSymbols; --naux >= 0;)
					AddAuxCOFFSymbol(*++symp);
			else
				symp += symp->NumberOfAuxSymbols;
		}
	}
}

void LinkJob::RelocateStabs(const ObjectFile& of, const byte *begin)
{
	LTIMING("LinkJob::RelocateStabs");

	if(of.stab_index < 0 || of.stabstr_index < 0)
		return;
	if(dump_flags & DUMP_STAB_TYPES)
		puts(NFormat("Relocating STABs for object '%s':", AsString(of)));
	const char *stabstrtbl = (const char *)begin + of.sections[of.stabstr_index].offset;
	const ObjectFile::Section& sec = of.sections[of.stab_index];
	ASSERT(sizeof(STAB_INFO) == 12);
	int count = sec.size / sizeof(STAB_INFO);
	STAB_INFO aux_stab, *stab = &aux_stab;
	STAB_INFO aux_stab2, *stab2 = &aux_stab2;
//	int match_inc = -1;
//	int begin_inc = -1;
	int fileatom = 0;
	Vector<bool> incl_stack;
	bool incl_use = true;
	int func_use = -1;
//	Stab filestab;
	const COFF_IMAGE_FILE_HEADER *header = (const COFF_IMAGE_FILE_HEADER *)begin;
	const COFF_IMAGE_SECTION_HEADER *secptr = (const COFF_IMAGE_SECTION_HEADER *)(begin
		+ sizeof(COFF_IMAGE_FILE_HEADER) + header->SizeOfOptionalHeader) + (of.stab_index - 1);
	const COFF_IMAGE_RELOCATION *reloc = (const COFF_IMAGE_RELOCATION *)(begin + secptr->PointerToRelocations);
	const COFF_IMAGE_SYMBOL *symtbl = (const COFF_IMAGE_SYMBOL *)(begin + header->PointerToSymbolTable), *symptr = symtbl;
	const char *strtbl = (const char *)(symtbl + header->NumberOfSymbols);
	Vector<int> stab_ref;
	stab_ref.SetCount(count, 0);

	for(int nreloc = secptr->NumberOfRelocations; --nreloc >= 0; reloc++) {
		int entry = reloc->VirtualAddress / sizeof(STAB_INFO);
		if(entry < 0 || entry >= count)
			continue;
		const COFF_IMAGE_SYMBOL& sym = symtbl[reloc->SymbolTableIndex];
		if(sym.SectionNumber > 0 && !(of.sections[sym.SectionNumber].flags & COFF_IMAGE_SCN_LNK_COMDAT))
			stab_ref[entry] = ~sym.SectionNumber;
		else if(sym.StorageClass == COFF_IMAGE_SYM_CLASS_EXTERNAL || sym.StorageClass == COFF_IMAGE_SYM_CLASS_STATIC)
			stab_ref[entry] = NameAtom(sym, strtbl);
	}

	Index<String> types_found;
	Index<String> type_stab_ident;
	Vector<int> type_stab_index;
	Vector<const char *> type_stab_def;

	LTIMING("LinkJob::RelocateStabs / stepping through stabs");
	int lastfile = 0;
	const byte *pstab = begin + sec.offset;
	int out_index = output_stab_info.GetCount() / STAB_STABSIZE;
	for(int istab = 0; istab < count; istab++, pstab += sizeof(STAB_INFO)) {
		GET_STAB(stab, pstab)
		if(stab->type == 0)
			continue;
		bool use_stab = !!func_use;
		byte outtype = stab->type;
//		Stab outstab;
//		outstab.type = stab->type;
		if(stab->type == N_BINCL) { // include matching
			LTIMING("LinkJob::RelocateStabs / checking includes");
			use_stab = true;
			int checksum = 0;
			int nesting = 0;
			const char *textptr = stabstrtbl + stab->strdx;
			const byte *npstab = pstab;
			int nstab = istab;
			while(++nstab < count) {
				npstab += sizeof(STAB_INFO);
				GET_STAB(stab2, npstab);
				if(stab2->type == N_EINCL) {
					if(--nesting < 0)
						break;
				}
				else if(stab2->type == N_BINCL)
					nesting++;
				else if(nesting == 0) {
					LTIMING("LinkJob::RelocateStabs / calculating checksums");
					const char *str = stabstrtbl + stab2->strdx;
					for(byte c; c = *str++;)
						if(c == '(') {
							while((byte)(*str++ - '0') < 10)
								;
							str--;
						}
						else if(*str == '(') {
							checksum += c;
							str++;
							while((byte)(*str++ - '0') < 10)
								;
							str--;
						}
						else if(*str == 0) {
							checksum += c;
							break;
						}
						else {
							checksum += c;
							str++;
						}
				}
			}
			String hash;
			int len = strlen(textptr);
			hash.Reserve(len + sizeof(checksum));
			hash.Cat(textptr, len);
			hash.Cat((const char *)&checksum, sizeof(checksum));
			incl_stack.Add(incl_use);
			int sc = stab_bincl_map.GetCount();
			incl_use = (stab_bincl_map.FindAdd(hash) >= sc);
//			incl_use = true;
			if(!incl_use)
				outtype = N_EXCL;
		}
		else if(stab->type == N_EINCL) {
			use_stab = incl_use;
			if(!incl_stack.IsEmpty())
				incl_use = incl_stack.Pop();
		}
		else if(stab->type == N_FUN) {
			if(stab->strdx) { // beginning
				int sr = stab_ref[istab];
				func_use = (sr < 0 ? of.sections[~sr].used : collected_symbols.Find(sr) >= 0) ? 1 : 0;
				use_stab = !!func_use;
			}
			else {
				func_use = -1;
			}
		}
		else if(func_use < 0 && (stab->type == N_STSYM || stab->type == N_LCSYM)) {
			int sr = stab_ref[istab];
			use_stab = (sr < 0 ? of.sections[~sr].used : collected_symbols.Find(sr) >= 0);
		}
		else
			use_stab &= incl_use;

		if(use_stab) {
			LTIMING("LinkJob::RelocateStabs / used stab");
			int value = stab->value;
			int ref = stab_ref[istab];
			int xsec;
			if(ref < 0 && (xsec = of.sections[~ref].sec_map_index) >= 0) {
				value += section_map[xsec].rva + code_rva + image_base;
//				highlow_fixup_rva.Add(out_index * STAB_STABSIZE + STAB_VALOFF + groups[GRP_STAB].rva + code_rva);
			}
			else if(ref > 0) {
				int c = collected_symbols.Find(ref);
				if(c >= 0) {
					const Symbol& sym = globals[collected_symbols[c]];
					value += sym.value + image_base;
//					highlow_fixup_rva.Add(out_index * STAB_STABSIZE + STAB_VALOFF + groups[GRP_STAB].rva + code_rva);
				}
			}
			int outdx = 0;
			if(stab->strdx) {
				const char *text = stabstrtbl + stab->strdx;
				int len = strlen(text) + 1;
				unsigned hash = memhash(text, len - 1);
				for(int f = output_stab_string_hash.Find(hash); f >= 0; f = output_stab_string_hash.FindNext(f)) {
					int pos = output_stab_string_hash[f];
					if(!memcmp(output_stab_strings.GetIter(pos), text, len)) {
						outdx = pos;
						break;
					}
				}
				if(!outdx) {
					outdx = output_stab_strings.GetCount();
					output_stab_strings.SetCountR(outdx + len);
					memcpy(output_stab_strings.GetIter(outdx), text, len);
					output_stab_string_hash.Add(hash, outdx);
				}
			}

			int ipos = output_stab_info.GetCount();
			output_stab_info.SetCountR(ipos + STAB_STABSIZE);
			byte *outstab = output_stab_info.GetIter(ipos);
			PokeIL(outstab + STAB_STRDXOFF, outdx);
			outstab[STAB_TYPEOFF] = outtype;
			outstab[STAB_OTHEROFF] = stab->other;
			PokeIW(outstab + STAB_DESCOFF, stab->desc);
			PokeIL(outstab + STAB_VALOFF, value);
			out_index++;
		}
	}
}

void LinkJob::CheckUnresolved()
{
	if(unresolved.IsEmpty())
		return;
	PutStdOut(NFormat("%d unresolved externals:", unresolved.GetCount()));
	Vector<int> unrn = unresolved.PickKeys();
	Sort(unrn, AtomOrder(*this));
	for(int i = 0; i < unrn.GetCount(); i++) {
		int uatom = unrn[i];
		PutStdOut(atoms[uatom]);
		int e = ext_obj_sec.Find(uatom);
		if(e >= 0)
			PutStdOut(NFormat("\t? referenced from %s", AsString(objects[ext_obj_sec[e].object])));
		for(int g = globals.Find(uatom); g >= 0; g = globals.FindNext(g)) {
			ObjSec os = globals[g].obj_sec;
			PutStdOut(NFormat("\t= defined in %s", FormatSection(os)));
		}
	}
}

String LinkJob::GetGroupName(int group)
{
	switch(group) {
	case GRP_CODE:    return "CODE";
	case GRP_RDATA:   return "RDATA";
	case GRP_EDATA:   return "EDATA";
	case GRP_IDATA:   return "IDATA";
	case GRP_DATA:    return "DATA";
	case GRP_UDATA:   return "BSS";
	case GRP_DEBUG:   return "DEBUG";
	case GRP_RSRC:    return "RSRC";
	case GRP_STAB:    return "STAB";
	case GRP_STABSTR: return "STABSTR";
	case GRP_SYMBOLS: return "SYMBOLS";
	default: NEVER(); return NFormat("(%d)", group);
	}
}

void LinkJob::WriteMapGlobals(String& map, const Vector<int>& used, String name)
{
	map <<
		"Section Offset   RVA      FPOS     Library      Object       " << name << "\n";

	for(int i = 0; i < used.GetCount(); i++) {
		const Symbol& sym = globals[used[i]];
		const ObjectFile *of = (sym.obj_sec.object >= 0 ? &objects[sym.obj_sec.object] : NULL);
		int atom = globals.GetKey(used[i]);
		String section;
		int value = sym.value;
		int section_rva = 0;
		int section_rfa = 0;
		int delta_rva = 0;
		int delta_rfa = 0;
		if(sym.obj_sec.object < 0)
			section = "LINKER";
		else if(sym.obj_sec.section < 0)
			section = (sym.obj_sec.section == -1 ? "ABS" : "DEBUG");
		else {
//			int secx = GetSection0(sym.obj_sec).sec_map_index;
			int secx = GetSection(sym.obj_sec).sec_map_index;
			if(secx >= 0) {
				const Section& sec = section_map[secx];
				section = atoms[section_name_map.GetKey(sec.app_section)];
				const Section& secbegin = section_map[section_name_map[sec.app_section]];
				section_rva = secbegin.rva + code_rva;
				section_rfa = (secbegin.rfa < 0 ? -1 : secbegin.rfa + code_rfa);
	//			delta_rva = code_rva;
	//			delta_rfa = code_rfa + section_rfa;
				if(!sym.used)
					value += section_rva;
			}
			else
				section = "UNUSED";
		}
		map << NFormat("%7<s %08x %08x %08x %12<s %12<s %s\n",
			section, value - section_rva, value, (section_rfa < 0 ? -1 : value - section_rva + section_rfa),
			of ? GetFileName(of->library_file) : String(Null),
			of ? GetFileName(of->object_file) : String(Null),
			DemangleAtom(atom));
	}
}

void LinkJob::WriteMapFile()
{
	int time = msecs();

	String map;
	map <<
		"Output file:  " << outputfile << "\n"
		"Timestamp:    " << FormatIntHex(timestamp) << ", " << ctime(&timestamp) <<
		"Load address: " << FormatIntHex(image_base) << "\n"
		"\n"
		"Segment  #   Class RVA       FPOS      Length\n";

	int i;
	for(i = 0; i < section_name_map.GetCount(); i++) {
		const Section& sec = section_map[section_name_map[i]];
		int lastid = (i + 1 >= section_name_map.GetCount() ? section_map.GetCount() : section_name_map[i + 1]) - 1;
		const Section& lastsec = section_map[lastid];
		String secname = atoms[section_name_map.GetKey(i)];
		map << NFormat("%8<s %3<d %5<s %08x  %08x  %08x\n",
			secname, i + 1, GetGroupName(sec.group), code_rva + sec.rva, code_rfa + sec.rfa, lastsec.rva - sec.rva
			+ GetSection(lastsec.obj_sec).size);
	}
	map << NFormat(".reloc %20>08x  %08x  %08x\n", groups[GRP_COUNT].rva, groups[GRP_COUNT].rfa, output_fixups.GetCount())
		<< "\n";

	Vector<int> used;
	for(i = 0; i < globals.GetCount(); i++) {
		bool u = globals[i].used || (dump_flags & DUMP_MAP_ALL);
		if(!u && (dump_flags & DUMP_MAP_UNUSED)) {
			const Symbol& sym = globals[i];
			if(sym.obj_sec.object >= 0 && sym.obj_sec.section >= 0 && GetSection(sym.obj_sec).used) {
				int f = i;
				while((f = globals.FindPrev(f)) >= 0) {
					const Symbol& prev = globals[f];
					if(prev.used || prev.obj_sec.object >= 0 && prev.obj_sec.section >= 0
						&& GetSection(prev.obj_sec).used)
						break;
				}
				if(f < 0)
					u = true;
			}
		}
		if(u)
			used.Add(i);
	}

	Sort(used, GlobalAddressOrder(*this));
	WriteMapGlobals(map, used, "Publics by address");
	map << "\n";
	Vector<String> demangled;
	demangled.SetCount(used.GetCount());
	for(i = 0; i < used.GetCount(); i++)
		demangled[i] = DemangleAtom(globals.GetKey(used[i]));
	IndexSort(demangled, used, GetLanguageInfo());
	WriteMapGlobals(map, used, "Publics by name");

	if(write_xref) {
		map << "\n"
			"Cross-reference map (Target: <- Referer)\n";
		for(int i = 0; i < used.GetCount(); i++) {
			int atom = globals.GetKey(used[i]);
			map << DemangleAtom(atom) << ":\n";
			Vector<String> referer;
			int f = collected_referer.Find(atom);
			if(f >= 0) {
				for(int r = 0; r < collected_referer[f].GetCount(); r++)
					referer.Add(DemangleAtom(collected_referer[f][r]));
				Sort(referer, GetLanguageInfo());
			}
			for(int r = 0; r < referer.GetCount(); r++)
				map << "\t<- " << referer[r] << "\n";
			if(referer.IsEmpty())
				map << "\t(root symbol)\n";
		}
	}

	map << "\n"
		"DLL Imports:\n";
	for(i = 0; i < used_dll_objects.GetCount(); i++) {
		const ObjectFile& of = objects[used_dll_objects[i]];
		map << NFormat("%s (%d imports)\n", of.object_file, of.used_imports.GetCount());
		for(int s = 0; s < of.used_imports.GetCount(); s++) {
			const ObjectFile::Import& imp = of.imports[of.used_imports[s]];
			map << NFormat("%5>d %s\n", imp.ordinal_hint, atoms[imp.imp_atom]);
		}
	}

	if(mapfile_stdout)
		PutStdOut(map);
	else {
		if(IsNull(mapfile)) {
			if(mapfile_stdout)
				return;
			mapfile = ForceExt(outputfile, ".map");
		}
		if(!SaveFile(mapfile, map)) {
			FileDelete(mapfile);
			throw Exc(NFormat("%s: error writing map file", mapfile));
		}

		PutStdOut(NFormat("%s(1): mapfile written in %d msecs", mapfile, msecs(time)));
	}
}

void LinkJob::BuildLibrary()
{
	int start = msecs();
	ArchiveJob job;
	job.machine = machine;
	job.keep_object_paths = true;
	job.use_existing_archive = false;
	job.archive_must_exist = false;
	//job.trim_longname_objects;
	job.verbose = verbose;
	String msg;
	outputfile = AppendExt(outputfile, ".lib");
	String msgline = NFormat("Writing library '%s'... ", outputfile);
	for(int i = OBJ_FIRST; i < objects.GetCount(); i++) {
		ObjectFile& of = objects[i];
		if(of.dll_atom || !IsNull(of.library_file))
			continue;
		FileMapping& mapping = GetMapping(of.object_file);
		if(!mapping.IsOpen())
			throw Exc(NFormat("%s: error opening file", of.object_file));
		of.archive_index = job.AddObject(of.object_file, mapping.GetData(), mapping.GetTime());
		String t = GetFileTitle(of.object_file);
		if(!msgline.IsEmpty() && msgline.GetLength() + t.GetLength() > 70) {
			msg << msgline << "\n\t";
			msgline = t;
		}
		else
			msgline << ' ' << t;
	}
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Archived objects collected in %d msecs.", msecs(start)));

	if(!IsNull(msgline))
		msg << msgline << '\n';
	start = msecs();
	for(int i = 0; i < globals.GetCount(); i++) {
		const Symbol& glo = globals[i];
		int ai;
		if(glo.type < COFF_IMAGE_SYM_TYPE_SPECIAL && glo.external
		&& glo.obj_sec.section > 0 && glo.obj_sec.object >= 0
		&& (ai = objects[glo.obj_sec.object].archive_index) >= 0)
			job.AddGlobal(ai, atoms[globals.GetKey(i)]);
	}
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Global symbols collected in %d msecs.", msecs(start)));
	if(verbose)
		PutStdOut(msg);
	start = msecs();
	job.BuildLibrary(outputfile, false);
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Library '%s' created in %d msecs.", GetFileName(outputfile), msecs(start)));
}

void LinkJob::BuildDefFiles()
{
	for(int i = OBJ_FIRST; i < objects.GetCount(); i++) {
		const ObjectFile& of = objects[i];
		if(!of.dll_atom || of.imports.IsEmpty())
			continue;
		int start = msecs();
		String output = ForceExt(of.library_file, ".def");
		PutStdOut(NFormat("%s -> %s", of.library_file, output));
		String data;
		data << "LIBRARY " << AsCString(atoms[of.dll_atom]) << "\n"
		"EXPORTS\n";
		Vector<int> order = GetSortOrder(of.imports, FieldRelation(&ObjectFile::Import::ordinal_hint, StdLess<int>()));
		for(int o = 0; o < order.GetCount(); o++) {
			const ObjectFile::Import& imp = of.imports[order[o]];
			String n = atoms[imp.app_atom];
			const char *p = n;
			if(IsAlpha(*p) || *p == '_')
				p++;
			while(IsAlNum(*p) || *p == '_')
				p++;
			if(*p)
				continue;
			data << "\t_" << n;
			if(imp.ordinal_hint)
				data << " @" << imp.ordinal_hint; // << " NONAME";
			data << '\n';
		}
		if(SaveFile(output, data))
			PutStdOut(NFormat(" (OK, %d B, %d msecs)\n", data.GetLength(), msecs(start)));
		else
			throw Exc(NFormat("Error writing DEF-file '%s' (%d B)\n", output, data.GetLength()));
	}
}

void LinkJob::Link()
{
	if(dump_flags & DUMP_ENVIRONMENT) {
		PutStdOut("Environment: <<");
		const VectorMap<String, String>& env = Environment();
		for(int i = 0; i < env.GetCount(); i++)
			PutStdOut(NFormat("%s=%s", env.GetKey(i), env[i]));
		PutStdOut(">> End of environment");
	}

	if(dump_flags & DUMP_COMMANDLINE) {
		PutStdOut(NFormat("Current directory: %s", GetCurrentDirectory()));
		PutStdOut("Command line: <<");
		for(int i = 0; i < user_command_args.GetCount(); i++)
			if(!IsNull(user_command_args[i]))
				PutStdOut(AsCString(user_command_args[i] + ' '));
		PutStdOut(">> End of command line");
	}

	PutStdOut(NFormat("Target machine: %s", COFFMachineName(machine)));
	switch(machine) {
		case COFF_IMAGE_FILE_MACHINE_I386: {
			imp_stub_size = I386_IMP_STUB_SIZE;
			imp_entry_size = I386_IMP_ENTRY_SIZE;
			break;
		}
		case COFF_IMAGE_FILE_MACHINE_ARM: {
			imp_stub_size = ARM_IMP_STUB_SIZE;
			imp_entry_size = ARM_IMP_ENTRY_SIZE;
			break;
		}
		default: {
			imp_stub_size = imp_entry_size = 0;
			break;
		}
	}

	for(int i = 0; i < files_to_load.GetCount(); i++)
		LoadFile(files_to_load[i], false);

	if(objects.GetCount() <= OBJ_FIRST && IsNull(outputfile))
		return;

	if(make_lib) {
		BuildLibrary();
		return;
	}

	if(make_def) {
		BuildDefFiles();
		return;
	}

	if(!image_base)
		if(machine == COFF_IMAGE_FILE_MACHINE_ARM)
			image_base = 0x10000;
		else if(!make_dll)
			image_base = 0x400000;
		else if(!auto_dll_base)
			image_base = 0x10000000;
		else
			image_base = 0x10000000 + ((GetHashValue(outputfile) % 0x6000) << 16);

	if(subsystem == COFF_IMAGE_SUBSYSTEM_DEFAULT)
#ifdef PLATFORM_WIN32
		subsystem = COFF_IMAGE_SUBSYSTEM_WINDOWS_CUI;
#else
		subsystem = COFF_IMAGE_SUBSYSTEM_POSIX;
#endif

	debug_info = (debug_info_raw > 0 || debug_info_raw != 0 && linkermode == MODE_GNULD);
	int start = msecs();
	ReadDefaultLibs();
	if(dump_flags & DUMP_TIMING) {
		PutStdOut(NFormat("Default libraries:   %d msecs", msecs(start)));
		PutStdOut(NFormat("Total load time:     %d msecs", msecs(start_time)));
	}
	start = msecs();
	SetEntryPoint();
	SetExportSymbols();
	SetRootSymbols();
	CollectSymbols();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Collecting symbols:  %d msecs", msecs(start)));
	start = msecs();
	CollectImports();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Collecting imports:  %d msecs", msecs(start)));
	start = msecs();
	CollectExports();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Collecting exports:  %d msecs", msecs(start)));
	start = msecs();
	CollectSections();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Collecting sections: %d msecs", msecs(start)));
	start = msecs();
	PrepareImageHeader();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Preparing header:    %d msecs", msecs(start)));
	start = msecs();
	RelocateGlobals();
	RelocateExports();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Relocating globals:  %d msecs", msecs(start)));
	start = msecs();
	WriteImageSections();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Composing sections:  %d msecs", msecs(start)));
	start = msecs();
	PrepareImageInfo();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Composing image:     %d msecs", msecs(start)));
	start = msecs();
	WriteImageFile();
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Writing image file:  %d msecs", msecs(start)));
	if(dump_flags & DUMP_TIMING)
		PutStdOut(NFormat("Total link time:     %d msecs", msecs(start_time)));
}

String LinkJob::FormatSection(ObjSec os) const
{
	if(IsNull(os))
		return "(null)";
	return NFormat("%s:%d", AsString(objects[os.object]), os.section);
}
