DIRECTORY Atom USING [MakeAtom], FS, IO, Rope USING [ROPE], ViewerClasses USING [Viewer], ViewerIO USING [CreateViewerStreams, GetViewerFromStream], ViewerOps USING [FindViewer, OpenIcon], VM USING [AddressForPageNumber, SimpleAllocate], MicroDefs, MicroGlobalVars, MicroOps, MicroUtils; MicroDebuggingImpl: CEDAR PROGRAM IMPORTS Atom, FS, IO, ViewerIO, ViewerOps, VM, MicroGlobalVars, MicroOps, MicroUtils = BEGIN OPEN MicroDefs, MicroUtils; printBuffer: LONG POINTER TO WORD; in, out: IO.STREAM; TSStream: PROC = { v: ViewerClasses.Viewer; name: ROPE = "Micro Debugging"; IF out = NIL THEN { v _ ViewerOps.FindViewer[name]; [in, out] _ ViewerIO.CreateViewerStreams[name, v, NIL, FALSE]; } ELSE v _ ViewerIO.GetViewerFromStream[out]; IF v#NIL THEN IF v.iconic THEN ViewerOps.OpenIcon[v]; }; Sc: PROC[ptr: INT, lx: INT] = { aPtr: LONG POINTER TO WORD _ LOOPHOLE[ptr]; TSStream[]; out.PutF["\n\n*** Show %g chars, starting at %bB\n\t", IO.int[lx], IO.int[ptr]]; FOR i: INT IN [0..lx) DO ch: CHAR = GetCharAtPointer[aPtr]; out.PutChar[' ]; IF ch > '\177 THEN { val: WORD; TRUSTED { val _ aPtr^ }; out.PutF["\\%03b", IO.int[val]] } ELSE IF ch < '\040 THEN out.PutF["^%g", IO.char[ch+100B]] ELSE out.PutChar[ch]; TRUSTED { aPtr _ aPtr + 1 }; ENDLOOP; out.PutChar['\n]; }; Sw: PROC[ptr: INT, lx: INT] = { aPtr: LONG POINTER TO WORD _ LOOPHOLE[ptr]; val: WORD; TSStream[]; out.PutF["\n\n*** Show %g words, starting at %bB\n\t", IO.int[lx], IO.int[ptr]]; FOR i: INT IN [0..lx) DO TRUSTED { val _ aPtr^; aPtr _ aPtr + 1; }; out.PutF[" %bB", IO.card[val]]; ENDLOOP; out.PutChar['\n]; }; Pmds: PROC[sName: ROPE] = { sObj: SymbolObj = Gs[sName]; mDef: REF INT; TSStream[]; IF sObj = NIL THEN { out.PutF["\n\n*** %g is not a symbol\n", IO.rope[sName]]; RETURN }; IF sObj.sType # macroType THEN { out.PutF["\n\n*** %g is not a macro\n", IO.rope[sName]]; RETURN }; mDef _ NARROW[sObj.sData]; ParseMacroDefn[MicroOps.MacroDefnFromIndex[mDef^], sName]; }; Pmd: PROC[offset: NAT] = { dPtr: LONG POINTER TO WORD _ MicroOps.MacroDefnFromIndex[offset]; ParseMacroDefn[dPtr, NIL]; }; Pa: PROC[ptr: INT] = { aPtr: LONG POINTER TO WORD; len: INTEGER; TSStream[]; TRUSTED { aPtr _ LOOPHOLE[ptr]; len _ aPtr^-1; aPtr _ aPtr + 1; }; out.PutF["\n\n*** Printing an argument of %g chars, starting at %g\n", IO.int[len], IO.int[ptr]]; IF len = 2 THEN { ch: CHAR; TRUSTED { ch _ GetCharAtPointer[aPtr+1] }; SELECT ch FROM symc => { PrintSymc[aPtr]; RETURN }; numc => { PrintNumc[aPtr]; RETURN }; num6c => { PrintNum6c[aPtr]; RETURN }; ENDCASE => NULL; }; FOR i: INTEGER IN [0..len) DO out.PutChar[GetCharAtPointer[aPtr]]; TRUSTED { aPtr _ aPtr + 1 }; ENDLOOP; }; Sym: PROC[symIndex: INTEGER] RETURNS[SymbolObj] = { RETURN[MicroOps.GetSymbol[symIndex] ] }; Gt: PROC[sName: ROPE] = { sObj: SymbolObj = Gs[sName]; typ: SymbolType; TSStream[]; IF sObj = NIL THEN { out.PutF["\n\n*** %g is not a symbol\n", IO.rope[sName]]; RETURN }; typ _ sObj.sType; IF typ = addressType THEN { memSym: INTEGER = sObj.sMisc; mObj: SymbolObj = MicroOps.GetSymbol[memSym]; out.PutF["\n\n %g is an address in Memory %g", IO.rope[sName], IO.rope[mObj.name]]; } ELSE out.PutF["\n\n %g is of type: %g\n", IO.rope[sName], IO.rope[MicroUtils.TypeName[typ]]]; }; St: PROC = { start: NAT = MicroGlobalVars.stmtTailBottom; end: NAT = 511; num: NAT = end - start + 1; TSStream[]; IF num = 0 THEN { out.PutRope["\n\n **** the tail of the stmtBuffer is empty\n"]; RETURN }; out.PutF["\n\n*** Printing %g chars [%g, 512) in tail of the stmt buffer\n", IO.int[num], IO.int[start] ]; PrintStmtBuffer[start, end]; }; Sb: PROC[start, num: NAT] = { end: NAT = start+num-1; TSStream[]; IF num = 0 THEN { out.PutRope["\n\n **** Num to print is zero\n"]; RETURN }; out.PutF["\n\n*** Printing %g chars [%g, %g) in the stmt buffer\n", IO.int[num], IO.int[start], IO.int[end] ]; PrintStmtBuffer[start, end]; }; Ps: PROC = { end: NAT = MicroGlobalVars.stmtBufferTop - 1; TSStream[]; IF end = 0 THEN { out.PutRope["\n\n **** Stmt buffer is empty\n"]; RETURN }; out.PutF["\n\n*** Printing the first %g chars in the stmt buffer\n", IO.int[end]]; PrintStmtBuffer[1, end]; }; Pst: PROC = { Ps[]; St[]; }; PrintStmtBuffer: PROC[start, end: NAT] = TRUSTED { current: NAT _ end; GetPsChar: PROC[which: NAT] RETURNS[CHAR] = TRUSTED INLINE { RETURN[MicroUtils.GetCharAtPointer[printBuffer+which]] }; WHILE current >= start DO ch: CHAR = MicroOps.GetStmtChar[current]; SELECT ch FROM symc, numc, num6c => { -- switch order (printBuffer+current)^ _ MicroOps.GetStmtValue[current-1]; (printBuffer+current-1)^ _ MicroOps.GetStmtValue[current]; current _ current - 1; }; ENDCASE => (printBuffer+current)^ _ MicroOps.GetStmtValue[current]; current _ current - 1; ENDLOOP; current _ start; WHILE current <= end DO ch: CHAR = GetPsChar[current]; SELECT ch FROM symc => { TRUSTED { PrintSymc[printBuffer+current+1] }; current _ current + 1; }; numc => { TRUSTED { PrintNumc[printBuffer+current+1] }; current _ current + 1; }; num6c => { TRUSTED { PrintNum6c[printBuffer+current+1] }; current _ current + 1; }; ENDCASE => out.PutChar[ch]; current _ current + 1; ENDLOOP; out.PutChar['\n]; }; ParseMacroDefn: PROC[dPtr: LONG POINTER TO WORD, name: ROPE] = { len: INTEGER; TSStream[]; TRUSTED { len _ dPtr^ }; IF len = 0 THEN { out.PutRope["\n\n ***** Zero length macro - ERROR *****\n"]; RETURN }; IF name # NIL THEN out.PutF["\n\n*** Parse macro defn for %g (%g words) starting at %bB\n", IO.rope[name], IO.int[len], IO.card[LOOPHOLE[dPtr]]] ELSE out.PutF["\n\n*** Parse macro defn (%g words) starting at %bB\n", IO.int[len], IO.card[LOOPHOLE[dPtr]]]; DO ch: CHAR; TRUSTED { dPtr _ dPtr + 1}; SELECT ch _ MicroUtils.GetCharAtPointer[dPtr] FROM Aargn => { param: INTEGER; TRUSTED { param _ ( dPtr _ dPtr + 1)^ }; out.PutF[" {Copy %gth arg} ", IO.int[param]]; }; Aarg1 => out.PutRope[" {Copy 1st arg} "]; Aarg2 => out.PutRope[" {Copy 2nd arg} "]; Anargs => out.PutRope[" {Give number of args} "]; symc => { TRUSTED { dPtr _ dPtr + 1}; PrintSymc[dPtr]; }; numc => { TRUSTED { dPtr _ dPtr + 1}; PrintNumc[dPtr]; }; num6c => { TRUSTED { dPtr _ dPtr + 1}; PrintNum6c[dPtr]; }; 40C => out.PutRope[" {40C - shouldn't happen}"]; Aend => { out.PutRope[" {End of macro defn}\n"]; EXIT; }; ENDCASE => out.PutChar[MicroUtils.GetCharAtPointer[dPtr]]; ENDLOOP; }; Gs: PROC[name: ROPE] RETURNS[sObj: MicroDefs.SymbolObj] = { symb: ATOM _ Atom.MakeAtom[name]; symIndex: INTEGER = MicroUtils.LookupAtom[symb]; IF symIndex = 0 THEN RETURN[NIL]; RETURN[MicroOps.GetSymbol[symIndex]]; }; GetValueAtPointer: PROC[ptr: LONG POINTER TO WORD] RETURNS[WORD] = TRUSTED { RETURN[ptr^] }; GetIntegerAtPointer: PROC[ptr: LONG POINTER TO WORD] RETURNS[INTEGER] = TRUSTED { RETURN[LOOPHOLE[ptr^, INTEGER]] }; GetNameAtPointer: PROC[ptr: LONG POINTER TO WORD] RETURNS[ROPE] = { symIndex: INTEGER = GetIntegerAtPointer[ptr]; RETURN[MicroOps.GetSymbol[symIndex].name]; }; GetStmtInteger: PROC[offset: NAT] RETURNS[INTEGER] = TRUSTED { RETURN[GetIntegerAtPointer[MicroGlobalVars.stmtBuffer+offset]] }; PrintSymc: PROC[aPtr: LONG POINTER TO WORD] = { out.PutF[" {symc: %g} ", IO.rope[GetNameAtPointer[aPtr]] ] }; PrintNumc: PROC[aPtr: LONG POINTER TO WORD] = { out.PutF[" {numc: %g} ", IO.int[GetIntegerAtPointer[aPtr]] ] }; PrintNum6c: PROC[aPtr: LONG POINTER TO WORD] = { out.PutF[" {symc: %bB} ", IO.card[GetValueAtPointer[aPtr]] ] }; ShowFixupsFile: PROC[name: ROPE] = { OPEN MicroOps; fx: STREAM; TSStream[]; out.PutF["\n\n*** Reading fixups file %g at %g\n", IO.rope[name], IO.time[]]; fx _ FS.StreamOpen[name ! FS.Error => { out.PutRope[error.explanation]; out.PutChar['\n]; CONTINUE} ]; IF fx = NIL THEN RETURN; DO IF fx.EndOf[] THEN EXIT; out.PutF["memIndex: %g, loc: %g, fePtr: %g, symIndex: %g,", IO.int[GetWord[fx]], IO.int[GetWord[fx]], IO.int[GetWord[fx]], IO.int[GetWord[fx]]]; out.PutF["\n\tlabelSymIndex: %g, lineCount: %g\n", IO.int[GetWord[fx]], IO.int[GetWord[fx]]]; ENDLOOP; fx.Close[]; }; CompareBinaryFiles: PROC[name1, name2: ROPE] = { OPEN MicroOps; fx1, fx2: STREAM; TSStream[]; out.PutF["\n\n*** Comparing the binary files %g & %g at %g\n", IO.rope[name1], IO.rope[name2], IO.time[]]; fx1 _ FS.StreamOpen[name1 ! FS.Error => { out.PutRope[error.explanation]; out.PutChar['\n]; CONTINUE} ]; IF fx1 = NIL THEN RETURN; fx2 _ FS.StreamOpen[name2 ! FS.Error => { out.PutRope[error.explanation]; out.PutChar['\n]; CONTINUE} ]; IF fx2 = NIL THEN { fx1.Close[]; RETURN }; fx1.SetIndex[0]; fx2.SetIndex[0]; DO b1, b2: INTEGER; IF fx1.EndOf[] OR fx2.EndOf[] THEN { IF fx1.EndOf[] AND fx2.EndOf[] THEN out.PutRope["\n\t\t*** the files are identical\n"] ELSE out.PutF["\n\t\t*** the files agree up to pos %g\n", IO.int[fx1.GetIndex[]] ]; EXIT; }; b1 _ GetInteger[fx1]; b2 _ GetInteger[fx2]; IF b1 = b2 THEN LOOP; out.PutF["*** files differ at pos %g: %g vs %g - quitting\n", IO.int[fx1.GetIndex[]-2], IO.int[b1], IO.int[b2]]; EXIT ENDLOOP; fx1.Close[]; fx2.Close[]; }; memWidths: ARRAY [0..30) OF INTEGER _ ALL[0]; ShowBinaryFile: PROC[name: ROPE, limit: INTEGER _ 20] = { OPEN MicroOps; fx: STREAM; firstFixup, firstSymbol: BOOL _ TRUE; memNum, count: INTEGER _ 0; TSStream[]; out.PutF["\n\n*** Reading binary file %g at %g\n", IO.rope[name], IO.time[]]; fx _ FS.StreamOpen[name ! FS.Error => { out.PutRope[error.explanation]; out.PutChar['\n]; CONTINUE} ]; IF fx = NIL THEN RETURN; out.PutF["\t\tFile is %g bytes long\n\n", IO.int[fx.GetLength[]] ]; fx.SetIndex[0]; DO bltkType: INTEGER; IF fx.EndOf[] THEN EXIT; bltkType _ GetInteger[fx]; IF (count _ count + 1) = limit THEN { ch: CHAR; out.PutRope["~~~ Continue? "]; IF (ch _ in.GetChar[]) = '\n OR ch = 'Y OR ch = 'y THEN { out.PutChar['\n]; count _ 0 } ELSE { fx.Close[]; RETURN }; }; SELECT bltkType FROM MBend => { out.PutRope["*** MBend\n"]; EXIT }; MBdata => { memWidth: INTEGER = memWidths[memNum]; out.PutF["*** MBdata. lineNum: %g\n\taccWord: ", IO.int[GetInteger[fx]]]; FOR i: INTEGER IN [0.. memWidth) DO out.PutF[" %b", IO.int[GetWord[fx]] ]; ENDLOOP; out.PutChar['\n]; }; MBseta => out.PutF["*** MBseta. memNum: %g, loc: %g\n", IO.int[memNum _ GetInteger[fx]], IO.int[GetInteger[fx]] ]; MBfixup => { IF firstFixup THEN { out.PutF["*** First Fixup at pos %g\n", IO.int[fx.GetIndex[]-2]]; firstFixup _ FALSE; }; out.PutF["*** MBfixup. memNum: %g, loc: %g, bits: %g, val: %g\n", IO.int[GetInteger[fx]], IO.int[GetInteger[fx]], IO.int[GetInteger[fx]], IO.int[GetInteger[fx]] ]; }; MBmemdef => { memNum: INTEGER = GetInteger[fx]; widthInBits: INTEGER = GetInteger[fx]; memWidths[memNum] _ (widthInBits+15)/16; out.PutF["*** MBmemdef. memNum: %g, width (bits): %g, name: ", IO.int[memNum], IO.int[widthInBits] ]; ReadSymName[fx]; out.PutChar['\n]; }; MBsymbol => { IF firstSymbol THEN { out.PutF["*** First Symbol at pos %g\n", IO.int[fx.GetIndex[]-2]]; firstSymbol _ FALSE; }; out.PutF["*** MBsymbol. memNum: %g, val: %g {", IO.int[GetInteger[fx]], IO.int[GetInteger[fx]] ]; ReadSymName[fx]; out.PutRope["}\n"]; }; MBext => { out.PutF["*** MBext. memNum: %g, loc: %g, bits: %g, {", IO.int[GetInteger[fx]], IO.int[GetInteger[fx]], IO.int[GetInteger[fx]] ]; ReadSymName[fx]; out.PutRope["}\n"]; }; ENDCASE => NULL; ENDLOOP; fx.Close[]; }; ReadSymName: PROC[fx: STREAM] = { DO ch: CHAR = fx.GetChar[]; IF ch = '\000 THEN EXIT; out.PutChar[ch]; ENDLOOP; IF fx.GetIndex[] MOD 2 = 1 THEN [] _ fx.GetChar[]; }; Acw: PROC[size: NAT] RETURNS[acWord: WordSeq] = { acWord _ NEW[WordSeqRec[size]]; FOR i: NAT IN [0..acWord.length) DO acWord[i] _ 0; ENDLOOP; }; Gf: PROC[field: ROPE, acWord: WordSeq] RETURNS[val: WORD] = { sObj: SymbolObj = Gs[field]; IF sObj = NIL THEN { out.PutF["\n %g is not a name\n", IO.rope[field]]; RETURN[0] }; IF sObj.sType # fieldType THEN { out.PutF["\n %g is not a field\n", IO.rope[field]]; RETURN[0] }; val _ MicroOps.GetBits[acWord, sObj.sMisc, LOOPHOLE[sObj.sVal, INTEGER]]; }; Set6: PROC[acWord: WordSeq, v0, v1, v2, v3, v4, v5: WORD] = { acWord[0] _ v0; acWord[1] _ v1; acWord[2] _ v2; acWord[3] _ v3; acWord[4] _ v4; acWord[5] _ v5; }; BEGIN buf: LONG POINTER _ VM.AddressForPageNumber[VM.SimpleAllocate[2].page]; printBuffer _ LOOPHOLE[buf, LONG POINTER TO WORD]; END; END. ΈMicroDebuggingImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Willie-sue, March 10, 1986 1:19:22 pm PST print stmt buffer's current contents start code Κ ˜šœ™Icodešœ Οmœ1™J˜Jšžœ'˜+—Jš žœžœžœžœ žœ˜5Jšœ˜—J˜šŸœžœžœžœ˜Jš œžœžœžœžœžœ˜+Jšœ ˜ Jšœ7žœ žœ ˜Pšžœžœžœ ž˜Jšœžœ˜"J˜šžœ žœ˜Jšœžœ˜ Jšžœ˜Jšœžœ ˜Jšœ˜Jšž˜šžœ žœ˜Jšœžœ˜!—Jšžœ˜—Jšžœ˜Jšžœ˜—J˜J˜—J˜šŸœžœžœžœ˜Jš œžœžœžœžœžœ˜+Jšœžœ˜ Jšœ ˜ Jšœ7žœ žœ ˜Pšžœžœžœ ž˜Jšžœ#˜*Jšœžœ ˜Jšžœ˜—J˜J˜—J˜šŸœžœžœ˜Jšœ˜Jšœžœžœ˜Jšœ ˜ šžœžœžœ˜Jšœ)žœ˜9Jšž˜J˜—šžœžœ˜ Jšœ(žœ˜8Jšž˜J˜—Jšœžœ ˜Jšœ:˜:J˜—J˜šŸœžœ žœ˜Jš œžœžœžœžœ'˜AJšœžœ˜J˜—J˜šŸœžœžœ˜Jš œžœžœžœžœ˜Jšœžœ˜ J˜ šžœ˜ Jšœžœ˜Jšœ˜Jšœ˜J˜—˜FJšžœ žœ ˜—šžœ žœ˜Jšœžœ˜ Jšžœ#˜*šžœž˜Jšœžœ˜$Jšœžœ˜$Jšœžœ˜&Jšžœžœ˜J˜——šžœžœžœ ž˜J˜$Jšžœ˜Jšžœ˜—J˜—J˜šŸœžœ žœžœ ˜1Jšœžœ"˜*—J˜šŸœžœžœ˜Jšœ˜Jšœ˜Jšœ ˜ šžœžœžœ˜Jšœ)žœ˜9Jšž˜J˜—J˜šžœžœ˜Jšœžœ˜Jšœ-˜-Jšœ/žœžœ˜SJ˜šžœ%˜)Jšžœžœ!˜3——J˜—J˜šŸœžœ˜ Jšœžœ"˜,Jšœžœ˜Jšœžœ˜Jšœ ˜ šžœ žœ˜J˜?Jšž˜J˜—šœL˜LJšžœ žœ˜—Jšœ˜Jšœ˜—J˜šŸœžœ žœ˜Jšœžœ˜Jšœ ˜ šžœ žœ˜J˜0Jšž˜J˜—šœC˜CJšžœ žœ žœ ˜*—Jšœ˜Jšœ˜—J˜šŸœžœ˜ J™$Jšœžœ%˜-Jšœ ˜ šžœ žœ˜J˜0Jšž˜J˜—JšœEžœ ˜RJ˜J˜—J˜JšŸœžœ˜J˜šŸœžœ žœžœ˜2Jšœ žœ˜š Ÿ œžœžœžœžœžœž˜:Jšœžœ3˜;—šžœž˜Jšœžœ!˜)šžœž˜šœΟc˜'Jšœ:˜:Jšœ:˜:Jšœ˜J˜—Jšžœ=˜DJšœ˜—Jšžœ˜—J˜Jšœ˜šžœž˜Jšœžœ˜šžœž˜šœ ˜ Jšžœ&˜-J˜J˜—šœ ˜ Jšžœ&˜-J˜J˜—šœ ˜ Jšžœ'˜.J˜J˜—Jšžœ˜—J˜—Jšžœ˜J˜J˜—J˜šŸœžœžœžœžœžœžœ˜@Jšœžœ˜ Jšœ ˜ Jšžœ˜šžœ žœ˜J˜˜>Jšžœžœžœ ˜+—šœžœžœ ˜)Jšœ˜J˜Jšžœ˜ —Jšžœžœžœžœ˜šœžœžœ ˜)Jšœ˜J˜Jšžœ˜ —Jšžœžœžœžœ˜*J˜!šž˜Jšœžœ˜šžœ žœ žœ˜$šœžœ žœ ž˜$Jšœ2˜2Jšžœ7žœ˜T—Jšžœ˜J˜—Jšœ+˜+Jšžœ žœžœ˜˜=Jšžœžœ žœ ˜2—Jšž˜Jšžœ˜—Jšœ˜J˜—J˜—Jš œ žœ žœžœžœ˜-˜šŸœžœžœ žœ ˜9Jšžœ ˜Jšœžœ˜ Jšœžœžœ˜%Jšœžœ˜J˜ Jšœ3žœ žœ ˜Mšœžœžœ ˜'Jšœ˜J˜Jšžœ˜ —Jšžœžœžœžœ˜Jšœ*žœ˜CJ˜šž˜Jšœ žœ˜Jšžœ žœžœ˜Jšœ˜šžœžœ˜%Jšœžœ˜ J˜šžœžœ žœ ž˜7Jšœ˜Jšžœžœ˜—J˜—šžœ ž˜šœ˜Jšœžœ˜&—šœ ˜ Jšœ žœ˜&Jšœ1žœ˜Jšžœžœžœž˜#Jšœžœ˜&Jšžœ˜—J˜J˜—šœ ˜ ˜.Jšžœžœ˜:——šœ ˜ šžœ žœ˜Jšœ(žœ˜AJšœ žœ˜J˜—šœA˜AJšžœžœ˜/Jšžœžœ˜1—J˜—šœ ˜ Jšœžœ˜!Jšœ žœ˜&Jšœ(˜(šœ>˜>Jšžœžœ˜&—J˜J˜J˜—šœ ˜ šžœ žœ˜Jšœ)žœ˜BJšœžœ˜J˜—šœ/˜/Jšžœžœ˜1—J˜J˜J˜—šœ ˜ šœ7˜7Jšžœžœžœ˜I—J˜J˜J˜—Jšžœžœ˜—Jšžœ˜—J˜ J˜—J˜šŸ œžœžœ˜!šž˜Jšœžœ˜Jšžœ žœžœ˜Jšœ˜Jšžœ˜—Jšžœžœžœ˜2J˜—J˜šŸœžœžœžœ˜1Jšœ žœ˜Jš žœžœžœžœžœ˜;J˜—J˜š Ÿœžœžœžœžœ˜=J˜šžœžœžœ˜Jšœ"žœ˜2Jšžœ˜ J˜—šžœžœ˜ Jšœ#žœ˜3Jšžœ˜ J˜—Jšœ+žœ žœ˜IJ˜—J˜šŸœžœ*žœ˜=Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜J˜——J˜šœ ™ J™šž˜Jš œžœžœžœžœ˜GJš œžœžœžœžœžœ˜2—Jšžœ˜J˜Jšžœ˜——…—/DB