private void addSourceLineInfo(BinaryReader reader)
			throws CancelledException, IOException, LockException {
		if (reader == null) {
			Msg.warn(this, "Can't add source line info - reader is null");
			return;
		}
		int entryCount = 0;
		Program ghidraProgram = prog.getGhidraProgram();
		long maxLength = prog.getImportOptions().getMaxSourceMapEntryLength();
		List<DWARFCompilationUnit> compUnits = prog.getCompilationUnits();
		monitor.initialize(compUnits.size(), "DWARF: Reading Source Map Info");
		SourceFileManager sourceManager = ghidraProgram.getSourceFileManager();
		List<SourceFileAddr> sourceInfo = new ArrayList<>();
		for (DWARFCompilationUnit cu : compUnits) {
			monitor.increment();
			sourceInfo.addAll(cu.getLine().getAllSourceFileAddrInfo(cu, reader));
		}
		monitor.setIndeterminate(true);
		monitor.setMessage("Sorting " + sourceInfo.size() + " entries");
		sourceInfo.sort((i, j) -> Long.compareUnsigned(i.address(), j.address()));
		monitor.setIndeterminate(false);
		monitor.initialize(sourceInfo.size(), "DWARF: Applying Source Map Info");
		Map<SourceFileAddr, SourceFile> sfasToSourceFiles = new HashMap<>();
		Set<SourceFileAddr> badSfas = new HashSet<>();
		AddressSet warnedAddresses = new AddressSet();

		for (int i = 0; i < sourceInfo.size() - 1; i++) {
			monitor.increment(1);
			SourceFileAddr sfa = sourceInfo.get(i);
			if (SOURCEFILENAMES_IGNORE.contains(sfa.fileName()) ||
				SOURCEFILENAMES_IGNORE.contains(FilenameUtils.getName(sfa.fileName())) ||
				sfa.isEndSequence()) {
				continue;
			}
			if (sfa.fileName() == null) {
				continue;
			}
			if (badSfas.contains(sfa)) {
				continue;
			}

			Address addr = prog.getCodeAddress(sfa.address());

			if (warnedAddresses.contains(addr)) {
				continue; // only warn once per address
			}
			if (!ghidraProgram.getMemory().getExecuteSet().contains(addr)) {
				String warningString =
					"entry for non-executable address; skipping: file %s line %d address: %s %x"
							.formatted(sfa.fileName(), sfa.lineNum(), addr.toString(),
								sfa.address());

				if (numSourceLineWarningReports++ < MAX_NUM_SOURCE_LINE_WARNING_REPORTS) {
					prog.logWarningAt(addr, addr.toString(), warningString);
				}
				warnedAddresses.add(addr);
				continue;
			}

			long length = getLength(i, sourceInfo);
			if (length < 0) {
				length = 0;
				String warningString =
					"Error calculating entry length for file %s line %d address %s %x; replacing " +
						"with length 0 entry".formatted(sfa.fileName(), sfa.lineNum(),
							addr.toString(), sfa.address());
				if (numSourceLineWarningReports++ < MAX_NUM_SOURCE_LINE_WARNING_REPORTS) {
					prog.logWarningAt(addr, addr.toString(), warningString);
				}
			}
			if (length > maxLength) {
				String warningString = ("entry for file %s line %d address: %s %x length %d too" +
					" large, replacing with length 0 entry").formatted(sfa.fileName(),
						sfa.lineNum(), addr.toString(), sfa.address(), length);
				length = 0;
				if (numSourceLineWarningReports++ < MAX_NUM_SOURCE_LINE_WARNING_REPORTS) {
					prog.logWarningAt(addr, addr.toString(), warningString);
				}
			}

			SourceFile source = sfasToSourceFiles.get(sfa);
			if (source == null) {
				try {
					String path = SourceFileUtils.normalizeDwarfPath(sfa.fileName(),
						DEFAULT_COMPILATION_DIR);
					SourceFileIdType type =
						sfa.md5() == null ? SourceFileIdType.NONE : SourceFileIdType.MD5;
					source = new SourceFile(path, type, sfa.md5());
					sourceManager.addSourceFile(source);
					sfasToSourceFiles.put(sfa, source);
				}
				catch (IllegalArgumentException e) {
					String errorString = "Exception creating source file: " + e.getMessage();
					if (numSourceLineErrorReports++ < MAX_NUM_SOURCE_LINE_ERROR_REPORTS) {
						reportError(errorString, addr);
					}
					badSfas.add(sfa);
					continue;
				}
			}

			try {
				sourceManager.addSourceMapEntry(source, sfa.lineNum(), addr, length);
			}
			catch (AddressOverflowException | IllegalArgumentException e) {
				String errorString = e.getClass().getName() + " for source map entry %s %d %s %x %d"
						.formatted(source.getFilename(), sfa.lineNum(), addr.toString(),
							sfa.address(), length);
				if (numSourceLineErrorReports++ < MAX_NUM_SOURCE_LINE_ERROR_REPORTS) {
					reportError(errorString, addr);
				}
				continue;
			}
			entryCount++;
		}
		if (numSourceLineWarningReports >= MAX_NUM_SOURCE_LINE_WARNING_REPORTS) {
			Msg.warn(this, "Additional warnings suppressed (%d total warnings)"
					.formatted(numSourceLineWarningReports));
		}
		if (numSourceLineErrorReports >= MAX_NUM_SOURCE_LINE_ERROR_REPORTS) {
			Msg.error(this, "Additional errors suppressed (%d total errors)"
					.formatted(numSourceLineErrorReports));
		}
		Msg.info(this, "Added %d source map entries".formatted(entryCount));
	}
Reads the dwarf source line info and applies it via the program's {@link SourceFileManager}.
Note that source file paths which are relative after normalization will have all leading
"." and "/../" entries stripped and then be placed under artificial directories based on
{@code DEFAULT_COMPILATION_DIR}.

@param reader reader
@throws CancelledException if cancelled by user
@throws IOException if error during reading
@throws LockException if invoked without exclusive access
Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.java