remote_gdb.cc revision 8332
11689SN/A/*
22326SN/A * Copyright (c) 2002-2005 The Regents of The University of Michigan
31689SN/A * All rights reserved.
41689SN/A *
51689SN/A * Redistribution and use in source and binary forms, with or without
61689SN/A * modification, are permitted provided that the following conditions are
71689SN/A * met: redistributions of source code must retain the above copyright
81689SN/A * notice, this list of conditions and the following disclaimer;
91689SN/A * redistributions in binary form must reproduce the above copyright
101689SN/A * notice, this list of conditions and the following disclaimer in the
111689SN/A * documentation and/or other materials provided with the distribution;
121689SN/A * neither the name of the copyright holders nor the names of its
131689SN/A * contributors may be used to endorse or promote products derived from
141689SN/A * this software without specific prior written permission.
151689SN/A *
161689SN/A * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
171689SN/A * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
181689SN/A * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
191689SN/A * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
201689SN/A * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
211689SN/A * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
221689SN/A * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
231689SN/A * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
241689SN/A * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
251689SN/A * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
261689SN/A * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
272665Ssaidi@eecs.umich.edu *
282665Ssaidi@eecs.umich.edu * Authors: Nathan Binkert
292831Sksewell@umich.edu */
301689SN/A
311689SN/A/*
322064SN/A * Copyright (c) 1990, 1993 The Regents of the University of California
331060SN/A * All rights reserved
341060SN/A *
352292SN/A * This software was developed by the Computer Systems Engineering group
361717SN/A * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
374762Snate@binkert.org * contributed to Berkeley.
386221Snate@binkert.org *
394762Snate@binkert.org * All advertising materials mentioning features or use of this software
401060SN/A * must display the following acknowledgement:
416221Snate@binkert.org *      This product includes software developed by the University of
425529Snate@binkert.org *      California, Lawrence Berkeley Laboratories.
431061SN/A *
442292SN/A * Redistribution and use in source and binary forms, with or without
455606Snate@binkert.org * modification, are permitted provided that the following conditions
465606Snate@binkert.org * are met:
475606Snate@binkert.org * 1. Redistributions of source code must retain the above copyright
481060SN/A *    notice, this list of conditions and the following disclaimer.
492292SN/A * 2. Redistributions in binary form must reproduce the above copyright
502292SN/A *    notice, this list of conditions and the following disclaimer in the
512292SN/A *    documentation and/or other materials provided with the distribution.
522292SN/A * 3. All advertising materials mentioning features or use of this software
532292SN/A *    must display the following acknowledgement:
542292SN/A *      This product includes software developed by the University of
552292SN/A *      California, Berkeley and its contributors.
562326SN/A * 4. Neither the name of the University nor the names of its contributors
572292SN/A *    may be used to endorse or promote products derived from this software
582292SN/A *    without specific prior written permission.
592292SN/A *
602292SN/A * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
612292SN/A * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
622292SN/A * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
635336Shines@cs.fsu.edu * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
642292SN/A * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
654873Sstever@eecs.umich.edu * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
662292SN/A * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
672292SN/A * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
682292SN/A * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
694329Sktlim@umich.edu * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
705529Snate@binkert.org * SUCH DAMAGE.
714329Sktlim@umich.edu *
724329Sktlim@umich.edu *      @(#)kgdb_stub.c 8.4 (Berkeley) 1/12/94
734329Sktlim@umich.edu */
742292SN/A
752292SN/A/*-
762292SN/A * Copyright (c) 2001 The NetBSD Foundation, Inc.
772292SN/A * All rights reserved.
782292SN/A *
792292SN/A * This code is derived from software contributed to The NetBSD Foundation
802292SN/A * by Jason R. Thorpe.
812292SN/A *
822307SN/A * Redistribution and use in source and binary forms, with or without
832307SN/A * modification, are permitted provided that the following conditions
845529Snate@binkert.org * are met:
851060SN/A * 1. Redistributions of source code must retain the above copyright
861060SN/A *    notice, this list of conditions and the following disclaimer.
871060SN/A * 2. Redistributions in binary form must reproduce the above copyright
881060SN/A *    notice, this list of conditions and the following disclaimer in the
891060SN/A *    documentation and/or other materials provided with the distribution.
901060SN/A * 3. All advertising materials mentioning features or use of this software
912326SN/A *    must display the following acknowledgement:
921060SN/A *      This product includes software developed by the NetBSD
931060SN/A *      Foundation, Inc. and its contributors.
941060SN/A * 4. Neither the name of The NetBSD Foundation nor the names of its
951060SN/A *    contributors may be used to endorse or promote products derived
962292SN/A *    from this software without specific prior written permission.
976221Snate@binkert.org *
986221Snate@binkert.org * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
996221Snate@binkert.org * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
1001060SN/A * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
1011060SN/A * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
1022307SN/A * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
1032292SN/A * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
1042980Sgblack@eecs.umich.edu * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
1052292SN/A * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1062292SN/A * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1072292SN/A * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
1082292SN/A * POSSIBILITY OF SUCH DAMAGE.
1092292SN/A */
1102292SN/A
1112292SN/A/*
1122292SN/A * $NetBSD: kgdb_stub.c,v 1.8 2001/07/07 22:58:00 wdk Exp $
1132292SN/A *
1142292SN/A * Taken from NetBSD
1156221Snate@binkert.org *
1166221Snate@binkert.org * "Stub" to allow remote cpu to debug over a serial line using gdb.
1172292SN/A */
1182292SN/A
1192292SN/A#include <sys/signal.h>
1202292SN/A#include <unistd.h>
1212292SN/A
1222292SN/A#include <cstdio>
1232292SN/A#include <string>
1242292SN/A
1252292SN/A#include "config/full_system.hh"
1266221Snate@binkert.org
1276221Snate@binkert.org#if FULL_SYSTEM
1282292SN/A#include "arch/vtophys.hh"
1292292SN/A#include "mem/vport.hh"
1302831Sksewell@umich.edu#endif
1312292SN/A
1322292SN/A#include "base/intmath.hh"
1332292SN/A#include "base/remote_gdb.hh"
1342292SN/A#include "base/socket.hh"
1352292SN/A#include "base/trace.hh"
1362292SN/A#include "config/the_isa.hh"
1372292SN/A#include "cpu/static_inst.hh"
1382292SN/A#include "cpu/thread_context.hh"
1392292SN/A#include "debug/GDBAll.hh"
1406221Snate@binkert.org#include "mem/port.hh"
1416221Snate@binkert.org#include "mem/translating_port.hh"
1422292SN/A#include "sim/system.hh"
1432292SN/A
1442831Sksewell@umich.eduusing namespace std;
1452292SN/Ausing namespace Debug;
1462292SN/Ausing namespace TheISA;
1472292SN/A
1482292SN/A#ifndef NDEBUG
1492292SN/Avector<BaseRemoteGDB *> debuggers;
1502292SN/A
1512292SN/Avoid
1522292SN/Adebugger()
1532292SN/A{
1542292SN/A    static int current_debugger = -1;
1552326SN/A    if (current_debugger >= 0 && current_debugger < (int)debuggers.size()) {
1562348SN/A        BaseRemoteGDB *gdb = debuggers[current_debugger];
1572326SN/A        if (!gdb->isattached())
1582326SN/A            gdb->listener->accept();
1592348SN/A        if (gdb->isattached())
1602292SN/A            gdb->trap(SIGILL);
1612292SN/A    }
1622292SN/A}
1632292SN/A#endif
1642292SN/A
1652292SN/A///////////////////////////////////////////////////////////
1662292SN/A//
1671060SN/A//
1681060SN/A//
1691061SN/A
1701060SN/AGDBListener::Event::Event(GDBListener *l, int fd, int e)
1711062SN/A    : PollEvent(fd, e), listener(l)
1721062SN/A{}
1732301SN/A
1741062SN/Avoid
1751062SN/AGDBListener::Event::process(int revent)
1761062SN/A{
1771062SN/A    listener->accept();
1781062SN/A}
1791062SN/A
1801062SN/AGDBListener::GDBListener(BaseRemoteGDB *g, int p)
1811062SN/A    : event(NULL), gdb(g), port(p)
1821062SN/A{
1831062SN/A    assert(!gdb->listener);
1842301SN/A    gdb->listener = this;
1852301SN/A}
1862301SN/A
1872301SN/AGDBListener::~GDBListener()
1881062SN/A{
1891062SN/A    if (event)
1901062SN/A        delete event;
1911062SN/A}
1921062SN/A
1931062SN/Astring
1941062SN/AGDBListener::name()
1951062SN/A{
1961062SN/A    return gdb->name() + ".listener";
1971062SN/A}
1981062SN/A
1991062SN/Avoid
2001062SN/AGDBListener::listen()
2011062SN/A{
2021062SN/A    if (ListenSocket::allDisabled()) {
2031062SN/A        warn_once("Sockets disabled, not accepting gdb connections");
2041062SN/A        return;
2051062SN/A    }
2061062SN/A
2071062SN/A    while (!listener.listen(port, true)) {
2081062SN/A        DPRINTF(GDBMisc, "Can't bind port %d\n", port);
2091062SN/A        port++;
2101062SN/A    }
2111062SN/A
2121062SN/A    event = new Event(this, listener.getfd(), POLLIN);
2131062SN/A    pollQueue.schedule(event);
2141062SN/A
2151062SN/A#ifndef NDEBUG
2161062SN/A    gdb->number = debuggers.size();
2171062SN/A    debuggers.push_back(gdb);
2181062SN/A#endif
2191062SN/A
2201062SN/A#ifndef NDEBUG
2211062SN/A    ccprintf(cerr, "%d: %s: listening for remote gdb #%d on port %d\n",
2221062SN/A             curTick(), name(), gdb->number, port);
2231062SN/A#else
2241062SN/A    ccprintf(cerr, "%d: %s: listening for remote gdb on port %d\n",
2251062SN/A             curTick(), name(), port);
2261062SN/A#endif
2271062SN/A}
2281062SN/A
2291062SN/Avoid
2301062SN/AGDBListener::accept()
2311062SN/A{
2321062SN/A    if (!listener.islistening())
2331062SN/A        panic("GDBListener::accept(): cannot accept if we're not listening!");
2341062SN/A
2352361SN/A    int sfd = listener.accept(true);
2362326SN/A
2372301SN/A    if (sfd != -1) {
2382301SN/A        if (gdb->isattached())
2392301SN/A            close(sfd);
2402301SN/A        else
2412301SN/A            gdb->attach(sfd);
2422301SN/A    }
2432326SN/A}
2442301SN/A
2452361SN/ABaseRemoteGDB::Event::Event(BaseRemoteGDB *g, int fd, int e)
2462326SN/A    : PollEvent(fd, e), gdb(g)
2472307SN/A{}
2482301SN/A
2492301SN/Avoid
2502307SN/ABaseRemoteGDB::Event::process(int revent)
2512301SN/A{
2522301SN/A    if (revent & POLLIN)
2532301SN/A        gdb->trap(SIGILL);
2542301SN/A    else if (revent & POLLNVAL)
2552301SN/A        gdb->detach();
2562301SN/A}
2572301SN/A
2582301SN/ABaseRemoteGDB::BaseRemoteGDB(System *_system, ThreadContext *c, size_t cacheSize)
2592301SN/A    : event(NULL), listener(NULL), number(-1), fd(-1),
2602301SN/A      active(false), attached(false),
2612301SN/A      system(_system), pmem(_system->physmem), context(c),
2622301SN/A      gdbregs(cacheSize)
2632326SN/A{
2644762Snate@binkert.org    memset(gdbregs.regs, 0, gdbregs.bytes());
2652301SN/A}
2662301SN/A
2672301SN/ABaseRemoteGDB::~BaseRemoteGDB()
2682301SN/A{
2694762Snate@binkert.org    if (event)
2702301SN/A        delete event;
2712301SN/A}
2722301SN/A
2732301SN/Astring
2742361SN/ABaseRemoteGDB::name()
2752326SN/A{
2762301SN/A    return system->name() + ".remote_gdb";
2772301SN/A}
2782301SN/A
2792301SN/Abool
2802301SN/ABaseRemoteGDB::isattached()
2812301SN/A{ return attached; }
2822301SN/A
2832980Sgblack@eecs.umich.eduvoid
2842301SN/ABaseRemoteGDB::attach(int f)
2852326SN/A{
2862301SN/A    fd = f;
2872361SN/A
2882326SN/A    event = new Event(this, fd, POLLIN);
2892301SN/A    pollQueue.schedule(event);
2902301SN/A
2912301SN/A    attached = true;
2922301SN/A    DPRINTFN("remote gdb attached\n");
2932326SN/A}
2942727Sktlim@umich.edu
2952326SN/Avoid
2962301SN/ABaseRemoteGDB::detach()
2972301SN/A{
2982301SN/A    attached = false;
2992301SN/A    close(fd);
3002301SN/A    fd = -1;
3012301SN/A
3024762Snate@binkert.org    pollQueue.remove(event);
3032301SN/A    DPRINTFN("remote gdb detached\n");
3042301SN/A}
3052326SN/A
3062301SN/Aconst char *
3072301SN/ABaseRemoteGDB::gdb_command(char cmd)
3082301SN/A{
3092301SN/A    switch (cmd) {
3102301SN/A      case GDBSignal: return "KGDB_SIGNAL";
3112301SN/A      case GDBSetBaud: return "KGDB_SET_BAUD";
3122326SN/A      case GDBSetBreak: return "KGDB_SET_BREAK";
3132301SN/A      case GDBCont: return "KGDB_CONT";
3142301SN/A      case GDBAsyncCont: return "KGDB_ASYNC_CONT";
3152301SN/A      case GDBDebug: return "KGDB_DEBUG";
3162301SN/A      case GDBDetach: return "KGDB_DETACH";
3172326SN/A      case GDBRegR: return "KGDB_REG_R";
3182301SN/A      case GDBRegW: return "KGDB_REG_W";
3196221Snate@binkert.org      case GDBSetThread: return "KGDB_SET_THREAD";
3202292SN/A      case GDBCycleStep: return "KGDB_CYCLE_STEP";
3216221Snate@binkert.org      case GDBSigCycleStep: return "KGDB_SIG_CYCLE_STEP";
3222292SN/A      case GDBKill: return "KGDB_KILL";
3237897Shestness@cs.utexas.edu      case GDBMemW: return "KGDB_MEM_W";
3247897Shestness@cs.utexas.edu      case GDBMemR: return "KGDB_MEM_R";
3257897Shestness@cs.utexas.edu      case GDBSetReg: return "KGDB_SET_REG";
3267897Shestness@cs.utexas.edu      case GDBReadReg: return "KGDB_READ_REG";
3277897Shestness@cs.utexas.edu      case GDBQueryVar: return "KGDB_QUERY_VAR";
3287897Shestness@cs.utexas.edu      case GDBSetVar: return "KGDB_SET_VAR";
3297897Shestness@cs.utexas.edu      case GDBReset: return "KGDB_RESET";
3307897Shestness@cs.utexas.edu      case GDBStep: return "KGDB_STEP";
3317897Shestness@cs.utexas.edu      case GDBAsyncStep: return "KGDB_ASYNC_STEP";
3327897Shestness@cs.utexas.edu      case GDBThreadAlive: return "KGDB_THREAD_ALIVE";
3337897Shestness@cs.utexas.edu      case GDBTargetExit: return "KGDB_TARGET_EXIT";
3347897Shestness@cs.utexas.edu      case GDBBinaryDload: return "KGDB_BINARY_DLOAD";
3357897Shestness@cs.utexas.edu      case GDBClrHwBkpt: return "KGDB_CLR_HW_BKPT";
3367897Shestness@cs.utexas.edu      case GDBSetHwBkpt: return "KGDB_SET_HW_BKPT";
3377897Shestness@cs.utexas.edu      case GDBStart: return "KGDB_START";
3387897Shestness@cs.utexas.edu      case GDBEnd: return "KGDB_END";
3397897Shestness@cs.utexas.edu      case GDBGoodP: return "KGDB_GOODP";
3407897Shestness@cs.utexas.edu      case GDBBadP: return "KGDB_BADP";
3417897Shestness@cs.utexas.edu      default: return "KGDB_UNKNOWN";
3427897Shestness@cs.utexas.edu    }
3437897Shestness@cs.utexas.edu}
3447897Shestness@cs.utexas.edu
3457897Shestness@cs.utexas.edu/////////////////////////
3467897Shestness@cs.utexas.edu//
3477897Shestness@cs.utexas.edu//
3487897Shestness@cs.utexas.edu
3497897Shestness@cs.utexas.eduuint8_t
3507897Shestness@cs.utexas.eduBaseRemoteGDB::getbyte()
3517897Shestness@cs.utexas.edu{
3527897Shestness@cs.utexas.edu    uint8_t b;
3537897Shestness@cs.utexas.edu    if (::read(fd, &b, 1) != 1)
3547897Shestness@cs.utexas.edu        warn("could not read byte from debugger");
3557897Shestness@cs.utexas.edu    return b;
3567897Shestness@cs.utexas.edu}
3577897Shestness@cs.utexas.edu
3587897Shestness@cs.utexas.eduvoid
3597897Shestness@cs.utexas.eduBaseRemoteGDB::putbyte(uint8_t b)
3607897Shestness@cs.utexas.edu{
3617897Shestness@cs.utexas.edu    if (::write(fd, &b, 1) != 1)
3627897Shestness@cs.utexas.edu        warn("could not write byte to debugger");
3637897Shestness@cs.utexas.edu}
3641062SN/A
3651062SN/A// Send a packet to gdb
3661062SN/Avoid
3671062SN/ABaseRemoteGDB::send(const char *bp)
3682307SN/A{
3691060SN/A    const char *p;
3702307SN/A    uint8_t csum, c;
3716221Snate@binkert.org
3726221Snate@binkert.org    DPRINTF(GDBSend, "send:  %s\n", bp);
3736221Snate@binkert.org
3742307SN/A    do {
3751060SN/A        p = bp;
3762307SN/A        //Start sending a packet
3772307SN/A        putbyte(GDBStart);
3782307SN/A        //Send the contents, and also keep a check sum.
3792307SN/A        for (csum = 0; (c = *p); p++) {
3802307SN/A            putbyte(c);
3812307SN/A            csum += c;
3822307SN/A        }
3832307SN/A        //Send the ending character.
3842307SN/A        putbyte(GDBEnd);
3852307SN/A        //Sent the checksum.
3862307SN/A        putbyte(i2digit(csum >> 4));
3872307SN/A        putbyte(i2digit(csum));
3886221Snate@binkert.org        //Try transmitting over and over again until the other end doesn't send an
3896221Snate@binkert.org        //error back.
3902307SN/A    } while ((c = getbyte() & 0x7f) == GDBBadP);
3912307SN/A}
3922307SN/A
3932307SN/A// Receive a packet from gdb
3942307SN/Aint
3952307SN/ABaseRemoteGDB::recv(char *bp, int maxlen)
3962307SN/A{
3972307SN/A    char *p;
3982307SN/A    int c, csum;
3992307SN/A    int len;
4001060SN/A
4011060SN/A    do {
4021061SN/A        p = bp;
4031060SN/A        csum = len = 0;
4046221Snate@binkert.org        //Find the beginning of a packet
4051060SN/A        while ((c = getbyte()) != GDBStart)
4062292SN/A            ;
4072064SN/A
4082064SN/A        //Read until you find the end of the data in the packet, and keep
4092064SN/A        //track of the check sum.
4102064SN/A        while ((c = getbyte()) != GDBEnd && len < maxlen) {
4112292SN/A            c &= 0x7f;
4122064SN/A            csum += c;
4134318Sktlim@umich.edu            *p++ = c;
4141060SN/A            len++;
4151060SN/A        }
4161061SN/A
4171060SN/A        //Mask the check sum, and terminate the command string.
4181060SN/A        csum &= 0xff;
4191060SN/A        *p = '\0';
4201060SN/A
4211060SN/A        //If the command was too long, report an error.
4221060SN/A        if (len >= maxlen) {
4231060SN/A            putbyte(GDBBadP);
4241060SN/A            continue;
4251684SN/A        }
4262307SN/A
4272307SN/A        //Bring in the checksum. If the check sum matches, csum will be 0.
4282307SN/A        csum -= digit2i(getbyte()) * 16;
4292367SN/A        csum -= digit2i(getbyte());
4302367SN/A
4312367SN/A        //If the check sum was correct
4322367SN/A        if (csum == 0) {
4332367SN/A            //Report that the packet was received correctly
4342367SN/A            putbyte(GDBGoodP);
4352367SN/A            // Sequence present?
4362307SN/A            if (bp[2] == ':') {
4372326SN/A                putbyte(bp[0]);
4382367SN/A                putbyte(bp[1]);
4392307SN/A                len -= 3;
4406221Snate@binkert.org                memcpy(bp, bp+3, len);
4416221Snate@binkert.org            }
4422307SN/A            break;
4432307SN/A        }
4442307SN/A        //Otherwise, report that there was a mistake.
4452307SN/A        putbyte(GDBBadP);
4462307SN/A    } while (1);
4472307SN/A
4482307SN/A    DPRINTF(GDBRecv, "recv:  %s: %s\n", gdb_command(*bp), bp);
4492307SN/A
4502307SN/A    return (len);
4512307SN/A}
4522307SN/A
4532292SN/A// Read bytes from kernel address space for debugger.
4546221Snate@binkert.orgbool
4552292SN/ABaseRemoteGDB::read(Addr vaddr, size_t size, char *data)
4562292SN/A{
4572292SN/A    static Addr lastaddr = 0;
4582292SN/A    static size_t lastsize = 0;
4592292SN/A
4602292SN/A    if (vaddr < 10) {
4612292SN/A      DPRINTF(GDBRead, "read:  reading memory location zero!\n");
4622292SN/A      vaddr = lastaddr + lastsize;
4632292SN/A    }
4642292SN/A
4652292SN/A    DPRINTF(GDBRead, "read:  addr=%#x, size=%d", vaddr, size);
4662292SN/A
4672292SN/A#if FULL_SYSTEM
4682292SN/A    VirtualPort *port = context->getVirtPort();
4693867Sbinkertn@umich.edu#else
4702292SN/A    TranslatingPort *port = context->getMemPort();
4716221Snate@binkert.org#endif
4726221Snate@binkert.org    port->readBlob(vaddr, (uint8_t*)data, size);
4732292SN/A
4743867Sbinkertn@umich.edu#if TRACING_ON
4756221Snate@binkert.org    if (DTRACE(GDBRead)) {
4763867Sbinkertn@umich.edu        if (DTRACE(GDBExtra)) {
4772292SN/A            char buf[1024];
4783867Sbinkertn@umich.edu            mem2hex(buf, data, size);
4792292SN/A            DPRINTFNR(": %s\n", buf);
4803867Sbinkertn@umich.edu        } else
4812292SN/A            DPRINTFNR("\n");
4822292SN/A    }
4832292SN/A#endif
4842292SN/A
4852292SN/A    return true;
4862292SN/A}
4871684SN/A
4881684SN/A// Write bytes to kernel address space for debugger.
4891684SN/Abool
4901684SN/ABaseRemoteGDB::write(Addr vaddr, size_t size, const char *data)
4911684SN/A{
4921684SN/A    static Addr lastaddr = 0;
4932292SN/A    static size_t lastsize = 0;
4942292SN/A
4956221Snate@binkert.org    if (vaddr < 10) {
4962292SN/A      DPRINTF(GDBWrite, "write: writing memory location zero!\n");
4972292SN/A      vaddr = lastaddr + lastsize;
4982292SN/A    }
4992292SN/A
5001060SN/A    if (DTRACE(GDBWrite)) {
5011060SN/A        DPRINTFN("write: addr=%#x, size=%d", vaddr, size);
5021061SN/A        if (DTRACE(GDBExtra)) {
5031060SN/A            char buf[1024];
5041060SN/A            mem2hex(buf, data, size);
5051060SN/A            DPRINTFNR(": %s\n", buf);
5061060SN/A        } else
5071060SN/A            DPRINTFNR("\n");
5081060SN/A    }
5091060SN/A#if FULL_SYSTEM
5101060SN/A    VirtualPort *port = context->getVirtPort();
5111060SN/A#else
5121060SN/A    TranslatingPort *port = context->getMemPort();
5131061SN/A#endif
5142292SN/A    port->writeBlob(vaddr, (uint8_t*)data, size);
5156221Snate@binkert.org#if !FULL_SYSTEM
5162292SN/A    delete port;
5172292SN/A#endif
5182292SN/A
5192292SN/A    return true;
5202292SN/A}
5212292SN/A
5222292SN/APCEventQueue *BaseRemoteGDB::getPcEventQueue()
5232292SN/A{
5242292SN/A    return &system->pcEventQueue;
5252292SN/A}
5262292SN/A
5272292SN/ABaseRemoteGDB::HardBreakpoint::HardBreakpoint(BaseRemoteGDB *_gdb, Addr pc)
5282292SN/A    : PCEvent(_gdb->getPcEventQueue(), "HardBreakpoint Event", pc),
5292292SN/A      gdb(_gdb), refcount(0)
5302292SN/A{
5312292SN/A    DPRINTF(GDBMisc, "creating hardware breakpoint at %#x\n", evpc);
5322292SN/A}
5332292SN/A
5342292SN/Avoid
5352292SN/ABaseRemoteGDB::HardBreakpoint::process(ThreadContext *tc)
5362292SN/A{
5372292SN/A    DPRINTF(GDBMisc, "handling hardware breakpoint at %#x\n", pc());
5382292SN/A
5392292SN/A    if (tc == gdb->context)
5402292SN/A        gdb->trap(SIGTRAP);
5412292SN/A}
5421060SN/A
5431061SN/Abool
5441060SN/ABaseRemoteGDB::insertSoftBreak(Addr addr, size_t len)
5457897Shestness@cs.utexas.edu{
5461060SN/A    if (len != sizeof(TheISA::MachInst))
5471060SN/A        panic("invalid length\n");
5481060SN/A
5497720Sgblack@eecs.umich.edu    return insertHardBreak(addr, len);
5507720Sgblack@eecs.umich.edu}
5511060SN/A
5521060SN/Abool
5531060SN/ABaseRemoteGDB::removeSoftBreak(Addr addr, size_t len)
5542292SN/A{
5551060SN/A    if (len != sizeof(MachInst))
5562064SN/A        panic("invalid length\n");
5571060SN/A
5582292SN/A    return removeHardBreak(addr, len);
5591060SN/A}
5601060SN/A
5611060SN/Abool
5621060SN/ABaseRemoteGDB::insertHardBreak(Addr addr, size_t len)
5631060SN/A{
5641060SN/A    if (len != sizeof(MachInst))
5651060SN/A        panic("invalid length\n");
5662326SN/A
5671060SN/A    DPRINTF(GDBMisc, "inserting hardware breakpoint at %#x\n", addr);
5681061SN/A
5692292SN/A    HardBreakpoint *&bkpt = hardBreakMap[addr];
5701062SN/A    if (bkpt == 0)
5711062SN/A        bkpt = new HardBreakpoint(this, addr);
5721061SN/A
5731061SN/A    bkpt->refcount++;
5741062SN/A
5751060SN/A    return true;
5762292SN/A}
5772292SN/A
5781060SN/Abool
5791060SN/ABaseRemoteGDB::removeHardBreak(Addr addr, size_t len)
5801060SN/A{
5811061SN/A    if (len != sizeof(MachInst))
5821061SN/A        panic("invalid length\n");
5832292SN/A
5841061SN/A    DPRINTF(GDBMisc, "removing hardware breakpoint at %#x\n", addr);
5851061SN/A
5861061SN/A    break_iter_t i = hardBreakMap.find(addr);
5877897Shestness@cs.utexas.edu    if (i == hardBreakMap.end())
5881061SN/A        return false;
5892292SN/A
5901061SN/A    HardBreakpoint *hbp = (*i).second;
5912292SN/A    if (--hbp->refcount == 0) {
5921061SN/A        delete hbp;
5937720Sgblack@eecs.umich.edu        hardBreakMap.erase(i);
5942326SN/A    }
5957720Sgblack@eecs.umich.edu
5962064SN/A    return true;
5971061SN/A}
5981061SN/A
5992292SN/Avoid
6001061SN/ABaseRemoteGDB::setTempBreakpoint(Addr bkpt)
6012064SN/A{
6021061SN/A    DPRINTF(GDBMisc, "setTempBreakpoint: addr=%#x\n", bkpt);
6032292SN/A    insertHardBreak(bkpt, sizeof(TheISA::MachInst));
6041061SN/A}
6051061SN/A
6061061SN/Avoid
6072326SN/ABaseRemoteGDB::clearTempBreakpoint(Addr &bkpt)
6081061SN/A{
6091061SN/A    DPRINTF(GDBMisc, "setTempBreakpoint: addr=%#x\n", bkpt);
6101061SN/A    removeHardBreak(bkpt, sizeof(TheISA::MachInst));
6112292SN/A    bkpt = 0;
6122292SN/A}
6131061SN/A
6141062SN/Aconst char *
6151062SN/ABaseRemoteGDB::break_type(char c)
6162292SN/A{
6172292SN/A    switch(c) {
6182292SN/A      case '0': return "software breakpoint";
6192292SN/A      case '1': return "hardware breakpoint";
6201061SN/A      case '2': return "write watchpoint";
6211061SN/A      case '3': return "read watchpoint";
6221061SN/A      case '4': return "access watchpoint";
6231060SN/A      default: return "unknown breakpoint/watchpoint";
6242292SN/A    }
6251060SN/A}
6262292SN/A
6271060SN/A// This function does all command processing for interfacing to a
6282292SN/A// remote gdb.  Note that the error codes are ignored by gdb at
6292292SN/A// present, but might eventually become meaningful. (XXX) It might
6301060SN/A// makes sense to use POSIX errno values, because that is what the
6312064SN/A// gdb/remote.c functions want to return.
6322333SN/Abool
6332333SN/ABaseRemoteGDB::trap(int type)
6342333SN/A{
6352333SN/A    uint64_t val;
6362333SN/A    size_t datalen, len;
6372333SN/A    char data[GDBPacketBufLen + 1];
6387897Shestness@cs.utexas.edu    char *buffer;
6397897Shestness@cs.utexas.edu    size_t bufferSize;
6407897Shestness@cs.utexas.edu    const char *p;
6417897Shestness@cs.utexas.edu    char command, subcmd;
6427897Shestness@cs.utexas.edu    string var;
6432333SN/A    bool ret;
6442333SN/A
6451060SN/A    if (!attached)
6462333SN/A        return false;
6472064SN/A
6482292SN/A    bufferSize = gdbregs.bytes() * 2 + 256;
6492292SN/A    buffer = (char*)malloc(bufferSize);
6502292SN/A
6512292SN/A    TheISA::PCState pc = context->pcState();
6522292SN/A    DPRINTF(GDBMisc, "trap: PC=%s\n", pc);
6532292SN/A
6542292SN/A    clearSingleStep();
6552292SN/A
6562292SN/A    /*
6572292SN/A     * The first entry to this function is normally through
6582292SN/A     * a breakpoint trap in kgdb_connect(), in which case we
6592292SN/A     * must advance past the breakpoint because gdb will not.
6602292SN/A     *
6612292SN/A     * On the first entry here, we expect that gdb is not yet
6622292SN/A     * listening to us, so just enter the interaction loop.
6632292SN/A     * After the debugger is "active" (connected) it will be
6642292SN/A     * waiting for a "signaled" message from us.
6652292SN/A     */
6662292SN/A    if (!active)
6671060SN/A        active = true;
6681060SN/A    else
6692292SN/A        // Tell remote host that an exception has occurred.
6702292SN/A        snprintf((char *)buffer, bufferSize, "S%02x", type);
6712292SN/A        send(buffer);
6721060SN/A
6732292SN/A    // Stick frame regs into our reg cache.
6742292SN/A    getregs();
6752292SN/A
6762292SN/A    for (;;) {
6772292SN/A        datalen = recv(data, sizeof(data));
6782292SN/A        data[sizeof(data) - 1] = 0; // Sentinel
6792292SN/A        command = data[0];
6802292SN/A        subcmd = 0;
6812292SN/A        p = data + 1;
6822292SN/A        switch (command) {
6832292SN/A
6842292SN/A          case GDBSignal:
6852292SN/A            // if this command came from a running gdb, answer it --
6862292SN/A            // the other guy has no way of knowing if we're in or out
6872292SN/A            // of this loop when he issues a "remote-signal".
6882292SN/A            snprintf((char *)buffer, bufferSize,
6892292SN/A                    "S%02x", type);
6902292SN/A            send(buffer);
6912292SN/A            continue;
6922292SN/A
6932292SN/A          case GDBRegR:
6941060SN/A            if (2 * gdbregs.bytes() > bufferSize)
6951060SN/A                panic("buffer too small");
6962292SN/A
6971060SN/A            mem2hex(buffer, gdbregs.regs, gdbregs.bytes());
6981060SN/A            send(buffer);
6992292SN/A            continue;
7002292SN/A
7012292SN/A          case GDBRegW:
7022292SN/A            p = hex2mem(gdbregs.regs, p, gdbregs.bytes());
7032367SN/A            if (p == NULL || *p != '\0')
7042292SN/A                send("E01");
7052292SN/A            else {
7062307SN/A                setregs();
7072367SN/A                send("OK");
7082367SN/A            }
7092307SN/A            continue;
7102307SN/A
7112307SN/A#if 0
7122292SN/A          case GDBSetReg:
7132292SN/A            val = hex2i(&p);
7142326SN/A            if (*p++ != '=') {
7152326SN/A                send("E01");
7162292SN/A                continue;
7172326SN/A            }
7182326SN/A            if (val < 0 && val >= KGDB_NUMREGS) {
7192326SN/A                send("E01");
7205327Smengke97@hotmail.com                continue;
7212333SN/A            }
7222292SN/A
7232292SN/A            gdbregs.regs[val] = hex2i(&p);
7241061SN/A            setregs();
7251061SN/A            send("OK");
7261061SN/A
7271061SN/A            continue;
7281060SN/A#endif
7291060SN/A
7301060SN/A          case GDBMemR:
7312292SN/A            val = hex2i(&p);
7322292SN/A            if (*p++ != ',') {
7331060SN/A                send("E02");
7341060SN/A                continue;
7351060SN/A            }
7362292SN/A            len = hex2i(&p);
7372292SN/A            if (*p != '\0') {
7382292SN/A                send("E03");
7392292SN/A                continue;
7402292SN/A            }
7412292SN/A            if (len > bufferSize) {
7422292SN/A                send("E04");
7432292SN/A                continue;
7442292SN/A            }
7452292SN/A            if (!acc(val, len)) {
7462292SN/A                send("E05");
7471060SN/A                continue;
7482333SN/A            }
7492820Sktlim@umich.edu
7502326SN/A            if (read(val, (size_t)len, (char *)buffer)) {
7512292SN/A               // variable length array would be nice, but C++ doesn't
7521060SN/A               // officially support those...
7532292SN/A               char *temp = new char[2*len+1];
7541060SN/A               mem2hex(temp, buffer, len);
7552292SN/A               send(temp);
7561060SN/A               delete [] temp;
7577897Shestness@cs.utexas.edu            } else {
7587897Shestness@cs.utexas.edu               send("E05");
7592292SN/A            }
7601060SN/A            continue;
7612292SN/A
7622292SN/A          case GDBMemW:
7631060SN/A            val = hex2i(&p);
7642292SN/A            if (*p++ != ',') {
7652292SN/A                send("E06");
7662292SN/A                continue;
7672292SN/A            }
7682292SN/A            len = hex2i(&p);
7691060SN/A            if (*p++ != ':') {
7701060SN/A                send("E07");
7712292SN/A                continue;
7721060SN/A            }
7732292SN/A            if (len > datalen - (p - data)) {
7742292SN/A                send("E08");
7752292SN/A                continue;
7761060SN/A            }
7771060SN/A            p = hex2mem(buffer, p, bufferSize);
7782326SN/A            if (p == NULL) {
7792326SN/A                send("E09");
7806221Snate@binkert.org                continue;
7811060SN/A            }
7822326SN/A            if (!acc(val, len)) {
7832326SN/A                send("E0A");
7847897Shestness@cs.utexas.edu                continue;
7852326SN/A            }
7862326SN/A            if (write(val, (size_t)len, (char *)buffer))
7871060SN/A              send("OK");
7881060SN/A            else
7891060SN/A              send("E0B");
7902348SN/A            continue;
7912348SN/A
7922326SN/A          case GDBSetThread:
7932292SN/A            subcmd = *p++;
7942292SN/A            val = hex2i(&p);
7952333SN/A            if (val == 0)
7961060SN/A                send("OK");
7972326SN/A            else
7982326SN/A                send("E01");
7992326SN/A            continue;
8002326SN/A
8012292SN/A          case GDBDetach:
8022292SN/A          case GDBKill:
8032326SN/A            active = false;
8042326SN/A            clearSingleStep();
8052326SN/A            detach();
8061060SN/A            goto out;
8077823Ssteve.reinhardt@amd.com
8081060SN/A          case GDBAsyncCont:
8092326SN/A            subcmd = hex2i(&p);
8102292SN/A            if (*p++ == ';') {
8112348SN/A                val = hex2i(&p);
8122348SN/A                context->pcState(val);
8132326SN/A            }
8142292SN/A            clearSingleStep();
8152292SN/A            goto out;
8162326SN/A
8172292SN/A          case GDBCont:
8181060SN/A            if (p - data < (ptrdiff_t)datalen) {
8191060SN/A                val = hex2i(&p);
8207720Sgblack@eecs.umich.edu                context->pcState(val);
8212292SN/A            }
8227720Sgblack@eecs.umich.edu            clearSingleStep();
8232292SN/A            goto out;
8241060SN/A
8252292SN/A          case GDBAsyncStep:
8261061SN/A            subcmd = hex2i(&p);
8272292SN/A            if (*p++ == ';') {
8282292SN/A                val = hex2i(&p);
8292292SN/A                context->pcState(val);
8302292SN/A            }
8312292SN/A            setSingleStep();
8321060SN/A            goto out;
8331060SN/A
8342064SN/A          case GDBStep:
8352292SN/A            if (p - data < (ptrdiff_t)datalen) {
8362064SN/A                val = hex2i(&p);
8372292SN/A                context->pcState(val);
8382292SN/A            }
8392292SN/A            setSingleStep();
8402292SN/A            goto out;
8412301SN/A
8422731Sktlim@umich.edu          case GDBClrHwBkpt:
8432292SN/A            subcmd = *p++;
8442301SN/A            if (*p++ != ',') send("E0D");
8452292SN/A            val = hex2i(&p);
8462292SN/A            if (*p++ != ',') send("E0D");
8472292SN/A            len = hex2i(&p);
8482326SN/A
8492820Sktlim@umich.edu            DPRINTF(GDBMisc, "clear %s, addr=%#x, len=%d\n",
8502292SN/A                    break_type(subcmd), val, len);
8512326SN/A
8522326SN/A            ret = false;
8532292SN/A
8541060SN/A            switch (subcmd) {
8551060SN/A              case '0': // software breakpoint
8561062SN/A                ret = removeSoftBreak(val, len);
8572326SN/A                break;
8582326SN/A
8592307SN/A              case '1': // hardware breakpoint
8602348SN/A                ret = removeHardBreak(val, len);
8612292SN/A                break;
8622292SN/A
8632292SN/A              case '2': // write watchpoint
8642292SN/A              case '3': // read watchpoint
8652292SN/A              case '4': // access watchpoint
8661060SN/A              default: // unknown
8671060SN/A                send("");
8681061SN/A                break;
8691060SN/A            }
8701061SN/A
8711060SN/A            send(ret ? "OK" : "E0C");
8722292SN/A            continue;
8732292SN/A
8741062SN/A          case GDBSetHwBkpt:
8752292SN/A            subcmd = *p++;
8761060SN/A            if (*p++ != ',') send("E0D");
8771061SN/A            val = hex2i(&p);
8781060SN/A            if (*p++ != ',') send("E0D");
8796221Snate@binkert.org            len = hex2i(&p);
8802292SN/A
8814033Sktlim@umich.edu            DPRINTF(GDBMisc, "set %s, addr=%#x, len=%d\n",
8824033Sktlim@umich.edu                    break_type(subcmd), val, len);
8831061SN/A
8841060SN/A            ret = false;
8851062SN/A
8861062SN/A            switch (subcmd) {
8871062SN/A              case '0': // software breakpoint
8882292SN/A                ret = insertSoftBreak(val, len);
8891062SN/A                break;
8901060SN/A
8912292SN/A              case '1': // hardware breakpoint
8922292SN/A                ret = insertHardBreak(val, len);
8931061SN/A                break;
8941060SN/A
8951060SN/A              case '2': // write watchpoint
8961061SN/A              case '3': // read watchpoint
8971061SN/A              case '4': // access watchpoint
8986221Snate@binkert.org              default: // unknown
8992292SN/A                send("");
9002292SN/A                break;
9012292SN/A            }
9022292SN/A
9032292SN/A            send(ret ? "OK" : "E0C");
9042292SN/A            continue;
9052292SN/A
9062292SN/A          case GDBQueryVar:
9072292SN/A            var = string(p, datalen - 1);
9082292SN/A            if (var == "C")
9092292SN/A                send("QC0");
9102292SN/A            else
9112292SN/A                send("");
9122292SN/A            continue;
9132292SN/A
9142292SN/A          case GDBSetBaud:
9152301SN/A          case GDBSetBreak:
9161684SN/A          case GDBDebug:
9171684SN/A          case GDBCycleStep:
9182301SN/A          case GDBSigCycleStep:
9192301SN/A          case GDBReadReg:
9207897Shestness@cs.utexas.edu          case GDBSetVar:
9217897Shestness@cs.utexas.edu          case GDBReset:
9227897Shestness@cs.utexas.edu          case GDBThreadAlive:
9237897Shestness@cs.utexas.edu          case GDBTargetExit:
9247897Shestness@cs.utexas.edu          case GDBBinaryDload:
9257897Shestness@cs.utexas.edu            // Unsupported command
9267897Shestness@cs.utexas.edu            DPRINTF(GDBMisc, "Unsupported command: %s\n",
9272292SN/A                    gdb_command(command));
9282292SN/A            DDUMP(GDBMisc, (uint8_t *)data, datalen);
9292292SN/A            send("");
9301684SN/A            continue;
9311684SN/A
9322292SN/A          default:
9332326SN/A            // Unknown command.
9342326SN/A            DPRINTF(GDBMisc, "Unknown command: %c(%#x)\n",
9352326SN/A                    command, command);
9362326SN/A            send("");
9371684SN/A            continue;
9382292SN/A
9392292SN/A
9402292SN/A        }
9412292SN/A    }
9422292SN/A
9431684SN/A  out:
9441684SN/A    free(buffer);
9451684SN/A    return true;
9461684SN/A}
9471684SN/A
9481684SN/A// Convert a hex digit into an integer.
9491684SN/A// This returns -1 if the argument passed is no valid hex digit.
9501684SN/Aint
9511684SN/ABaseRemoteGDB::digit2i(char c)
9521684SN/A{
9531684SN/A    if (c >= '0' && c <= '9')
9541684SN/A        return (c - '0');
9551684SN/A    else if (c >= 'a' && c <= 'f')
9567599Sminkyu.jeong@arm.com        return (c - 'a' + 10);
9577599Sminkyu.jeong@arm.com    else if (c >= 'A' && c <= 'F')
9581684SN/A
9591684SN/A        return (c - 'A' + 10);
9601684SN/A    else
9612292SN/A        return (-1);
9621684SN/A}
9631684SN/A
9642326SN/A// Convert the low 4 bits of an integer into an hex digit.
9652326SN/Achar
9662326SN/ABaseRemoteGDB::i2digit(int n)
9671684SN/A{
9682326SN/A    return ("0123456789abcdef"[n & 0x0f]);
9697599Sminkyu.jeong@arm.com}
9707720Sgblack@eecs.umich.edu
9711684SN/A// Convert a byte array into an hex string.
9721684SN/Avoid
9732326SN/ABaseRemoteGDB::mem2hex(void *vdst, const void *vsrc, int len)
9742326SN/A{
9752326SN/A    char *dst = (char *)vdst;
9762326SN/A    const char *src = (const char *)vsrc;
9771684SN/A
9782326SN/A    while (len--) {
9791684SN/A        *dst++ = i2digit(*src >> 4);
9802326SN/A        *dst++ = i2digit(*src++);
9811684SN/A    }
9822301SN/A    *dst = '\0';
9831684SN/A}
9841684SN/A
9852326SN/A// Convert an hex string into a byte array.
9862326SN/A// This returns a pointer to the character following the last valid
9872326SN/A// hex digit. If the string ends in the middle of a byte, NULL is
9882326SN/A// returned.
9891684SN/Aconst char *
9901684SN/ABaseRemoteGDB::hex2mem(void *vdst, const char *src, int maxlen)
9911684SN/A{
9921684SN/A    char *dst = (char *)vdst;
9932301SN/A    int msb, lsb;
9942064SN/A
9952064SN/A    while (*src && maxlen--) {
9962064SN/A        msb = digit2i(*src++);
9972064SN/A        if (msb < 0)
9982292SN/A            return (src - 1);
9992064SN/A        lsb = digit2i(*src++);
10002292SN/A        if (lsb < 0)
10012292SN/A            return (NULL);
10022292SN/A        *dst++ = (msb << 4) | lsb;
10032292SN/A    }
10042326SN/A    return (src);
10052326SN/A}
10062326SN/A
10072326SN/A// Convert an hex string into an integer.
10082326SN/A// This returns a pointer to the character following the last valid
10092326SN/A// hex digit.
10102326SN/AAddr
10112326SN/ABaseRemoteGDB::hex2i(const char **srcp)
10122326SN/A{
10132326SN/A    const char *src = *srcp;
10142292SN/A    Addr r = 0;
10157720Sgblack@eecs.umich.edu    int nibble;
10167720Sgblack@eecs.umich.edu
10172064SN/A    while ((nibble = digit2i(*src)) >= 0) {
10182064SN/A        r *= 16;
10192064SN/A        r += nibble;
10202064SN/A        src++;
10212292SN/A    }
10222064SN/A    *srcp = src;
10234033Sktlim@umich.edu    return (r);
10244033Sktlim@umich.edu}
10252292SN/A
10262064SN/A