/******************************************************************************* * Copyright (c) 2013, 2014 Xilinx, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Xilinx - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.internal.debug.model; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.tcf.internal.debug.launch.TCFLaunchDelegate; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IToken; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.services.IMemory; import org.eclipse.tcf.services.IRegisters; import org.eclipse.tcf.services.IRegisters.RegistersContext; import org.eclipse.tcf.services.IRunControl; public class ElfLoader implements Runnable { private final IChannel channel; /* Loader parameters */ private Map<String,Object> args; private boolean download; private boolean set_pc; private Runnable done; /* Services */ private final IRunControl service_rc; private final IMemory service_mem; private final IRegisters service_regs; /* Pending commands */ private final Set<IToken> cmds = new HashSet<IToken>(); /* Debug contexts */ private final Map<String,IRunControl.RunControlContext> contexts = new HashMap<String,IRunControl.RunControlContext>(); /* Running debug contexts */ private final Set<String> running = new HashSet<String>(); private List<Throwable> errors = new ArrayList<Throwable>(); private final IRunControl.RunControlListener rc_listener = new IRunControl.RunControlListener() { @Override public void contextSuspended(String id, String pc, String reason, Map<String, Object> params) { running.remove(id); run(); } @Override public void contextResumed(String id) { running.add(id); run(); } @Override public void contextRemoved(String[] context_ids) { for (String id : context_ids) { contexts.remove(id); running.remove(id); } run(); } @Override public void contextException(String context, String msg) { } @Override public void contextChanged(IRunControl.RunControlContext[] contexts) { } @Override public void contextAdded(IRunControl.RunControlContext[] arr) { for (IRunControl.RunControlContext ctx : arr) { String id = ctx.getID(); contexts.put(id, ctx); if (ctx.hasState()) running.add(id); } run(); } @Override public void containerSuspended(String context, String pc, String reason, Map<String, Object> params, String[] suspended_ids) { for (String id : suspended_ids) running.remove(id); run(); } @Override public void containerResumed(String[] context_ids) { for (String id : context_ids) running.add(id); run(); } }; private final IRunControl.DoneGetContext done_ctx_get_context = new IRunControl.DoneGetContext() { @Override public void doneGetContext(IToken token, Exception error, final IRunControl.RunControlContext context) { cmds.remove(token); if (error != null) { errors.add(error); } else { final String id = context.getID(); contexts.put(id, context); if (context.hasState()) { cmds.add(context.getState(new IRunControl.DoneGetState() { @Override public void doneGetState(IToken token, Exception error, boolean suspended, String pc, String reason, Map<String, Object> params) { cmds.remove(token); if (error != null) { errors.add(error); } else if (!suspended) { running.add(id); } run(); } })); } } run(); } }; private final IRunControl.DoneGetChildren done_ctx_get_children = new IRunControl.DoneGetChildren() { @Override public void doneGetChildren(IToken token, Exception error, String[] context_ids) { cmds.remove(token); if (error != null) { errors.add(error); } else if (context_ids != null) { for (String id : context_ids) { cmds.add(service_rc.getContext(id, done_ctx_get_context)); cmds.add(service_rc.getChildren(id, this)); } } run(); } }; private final IMemory.DoneGetContext done_mem_get_context = new IMemory.DoneGetContext() { @Override public void doneGetContext(IToken token, Exception error, IMemory.MemoryContext context) { cmds.remove(token); if (error != null) { errors.add(error); } else { assert context != null; mem_ctx = context; File fnm = null; try { fnm = new File((String)args.get(TCFLaunchDelegate.FILES_FILE_NAME)); file = new RandomAccessFile(fnm, "r"); try { downloadFile(context); } finally { file.close(); } } catch (Exception e) { if (fnm != null) e = new Exception("Cannot read '" + fnm.getName() + "'", e); errors.add(e); } file = null; } run(); } }; private final IRegisters.DoneGetChildren done_regs_get_children = new IRegisters.DoneGetChildren() { @Override public void doneGetChildren(IToken token, Exception error, String[] context_ids) { cmds.remove(token); if (error != null) errors.add(error); if (context_ids != null) { for (String id : context_ids) { cmds.add(service_regs.getContext(id, new IRegisters.DoneGetContext() { @Override public void doneGetContext(IToken token, Exception error, RegistersContext context) { cmds.remove(token); if (error != null) errors.add(error); if (context != null) { if (IRegisters.ROLE_PC.equals(context.getRole())) { reg_pc = context; setEntryAddress(); } else if (reg_pc == null && errors.size() == 0) { cmds.add(service_regs.getChildren(context.getID(), done_regs_get_children)); } } run(); } })); } } run(); } }; private static final int PT_LOAD = 1; private boolean listener_ok; private boolean started_context_retrieval; private boolean started_reginfo_retrieval; private boolean disposed; private RandomAccessFile file; private boolean big_endian; private boolean elf64; private IMemory.MemoryContext mem_ctx; private BigInteger entry_addr; private RegistersContext reg_pc; private long start_time; ElfLoader(IChannel channel) { this.channel = channel; service_rc = channel.getRemoteService(IRunControl.class); service_mem = channel.getRemoteService(IMemory.class); service_regs = channel.getRemoteService(IRegisters.class); } void load(Map<String,Object> args, Runnable done) { this.args = args; this.done = done; Boolean b1 = (Boolean)args.get(TCFLaunchDelegate.FILES_DOWNLOAD); Boolean b2 = (Boolean)args.get(TCFLaunchDelegate.FILES_SET_PC); download = b1 != null && b1.booleanValue(); set_pc = b2 != null && b2.booleanValue(); start_time = System.currentTimeMillis(); started_reginfo_retrieval = false; entry_addr = null; mem_ctx = null; reg_pc = null; Protocol.invokeLater(this); } void dispose() { if (service_rc != null) service_rc.removeListener(rc_listener); disposed = true; } private BigInteger readNumberX() throws IOException { int size = elf64 ? 8 : 4; byte[] buf = new byte[size + 1]; file.readFully(buf, 1, size); if (!big_endian) { for (int i = 0; i < size / 2; i++) { byte x = buf[i + 1]; buf[i + 1] = buf[size - i]; buf[size - i] = x; } } return new BigInteger(buf); } private int readInt2() throws IOException { int x = file.readUnsignedByte(); int y = file.readUnsignedByte(); return big_endian ? (x << 8) + y : x + (y << 8); } private int readInt4() throws IOException { int x = readInt2(); int y = readInt2(); return big_endian ? (x << 16) + y : x + (y << 16); } private void downloadFile(IMemory.MemoryContext context) throws Exception { if (file.readByte() != 0x7f || file.readByte() != 'E' || file.readByte() != 'L' || file.readByte() != 'F') throw new IOException("Not an ELF file"); switch (file.readByte()) { case 1: elf64 = false; break; case 2: elf64 = true; break; default: throw new IOException("Invalid ELF file"); } switch (file.readByte()) { case 1: big_endian = false; break; case 2: big_endian = true; break; default: throw new IOException("Invalid ELF file"); } file.seek(24); entry_addr = readNumberX(); if (download) { BigInteger phoff = readNumberX(); @SuppressWarnings("unused") BigInteger shoff = readNumberX(); file.seek(file.getFilePointer() + 6); int phentsize = readInt2(); int phnum = readInt2(); for (int n = 0; n < phnum; n++) { file.seek(phoff.longValue() + n * phentsize); int p_type = readInt4(); if (p_type != PT_LOAD) continue; if (elf64) readInt4(); BigInteger p_offset = readNumberX(); @SuppressWarnings("unused") BigInteger p_vaddr = readNumberX(); BigInteger p_paddr = readNumberX(); BigInteger p_filesz = readNumberX(); BigInteger p_memsz = readNumberX(); byte buf[] = new byte[p_filesz.intValue()]; file.seek(p_offset.longValue()); file.readFully(buf); cmds.add(context.set(p_paddr, 4, buf, 0, buf.length, 0, new IMemory.DoneMemory() { @Override public void doneMemory(IToken token, IMemory.MemoryError error) { cmds.remove(token); if (error != null) errors.add(error); run(); } })); BigInteger fill = p_memsz.subtract(p_filesz); if (fill.compareTo(BigInteger.ZERO) > 0) { buf = new byte[4]; cmds.add(context.fill(p_paddr.add(p_filesz), 4, buf, fill.intValue(), 0, new IMemory.DoneMemory() { @Override public void doneMemory(IToken token, IMemory.MemoryError error) { cmds.remove(token); if (error != null) errors.add(error); run(); } })); } } } } private void setEntryAddress() { byte[] value = new byte[reg_pc.getSize()]; boolean big_endian = reg_pc.isBigEndian(); BigInteger n = entry_addr; for (int i = 0; i < value.length; i++) { value[big_endian ? value.length - i - 1 : i] = n.byteValue(); n = n.shiftRight(8); } cmds.add(reg_pc.set(value, new IRegisters.DoneSet() { @Override public void doneSet(IToken token, Exception error) { cmds.remove(token); if (error != null) errors.add(error); run(); } })); } private String getFullName(IRunControl.RunControlContext ctx) { if (ctx == null) return null; String name = ctx.getName(); if (name == null) name = ctx.getID(); String parent = ctx.getParentID(); if (parent == null) return "/" + name; String path = getFullName(contexts.get(parent)); if (path == null) return null; return path + '/' + name; } private boolean should_stop(String id) { /* We should suspend only contexts in same run control group as target context */ String target_id = (String)args.get(TCFLaunchDelegate.FILES_CONTEXT_ID); if (target_id == null) return false; IRunControl.RunControlContext ctx = contexts.get(id); if (ctx == null) return false; if (!ctx.hasState()) return false; if (id.equals(target_id)) return true; String group = ctx.getRCGroup(); if (group == null) return false; IRunControl.RunControlContext target_ctx = contexts.get(target_id); if (target_ctx == null) return false; String target_group = target_ctx.getRCGroup(); if (target_group == null) return false; if (target_group.equals(group)) return true; return false; } public void run() { /* Wait for pending commands */ if (cmds.size() > 0) return; if (disposed) return; if (done == null) return; if (service_rc == null || service_mem == null) { errors.add(new Error("No services, cannot do anything")); } if (!listener_ok) { service_rc.addListener(rc_listener); listener_ok = true; } if (errors.size() == 0 && !started_context_retrieval) { /* Retrieve debug context information */ cmds.add(service_rc.getChildren(null, done_ctx_get_children)); started_context_retrieval = true; return; } if (errors.size() == 0 && running.size() > 0) { /* Suspend target context */ for (final String id : running) { if (!should_stop(id)) continue; if (System.currentTimeMillis() - start_time < 5000) { cmds.add(contexts.get(id).suspend(new IRunControl.DoneCommand() { @Override public void doneCommand(IToken token, Exception error) { cmds.remove(token); if (error != null && running.contains(id)) errors.add(error); run(); } })); } else { String name = contexts.get(id).getName(); if (name == null) name = id; errors.add(new Exception("Cannot stop " + name)); } } if (cmds.size() > 0) return; } if (errors.size() == 0 && mem_ctx == null) { /* Download the file */ String id = (String)args.get(TCFLaunchDelegate.FILES_CONTEXT_ID); if (id != null && contexts.get(id) != null) { cmds.add(service_mem.getContext(id, done_mem_get_context)); return; } String name = (String)args.get(TCFLaunchDelegate.FILES_CONTEXT_FULL_NAME); if (name != null) { for (IRunControl.RunControlContext ctx : contexts.values()) { if (name.equals(getFullName(ctx))) { cmds.add(service_mem.getContext(ctx.getID(), done_mem_get_context)); return; } } } /* Wait for context */ if (System.currentTimeMillis() - start_time < 5000) { Protocol.invokeLater(200, this); return; } errors.add(new Exception( "Context not found: " + (name != null ? name : id))); } if (errors.size() == 0 && mem_ctx != null && entry_addr != null && set_pc && !started_reginfo_retrieval && service_regs != null) { started_reginfo_retrieval = true; cmds.add(service_regs.getChildren(mem_ctx.getID(), done_regs_get_children)); return; } /* All done */ if (errors.size() > 0) channel.terminate(new Exception("Download error", errors.get(0))); Protocol.invokeLater(done); done = null; } }