Skip to content

Instantly share code, notes, and snippets.

@lupyuen
Last active March 2, 2024 04:00
Show Gist options
  • Save lupyuen/1405685d6f847ea5d4d6302b196bb05e to your computer and use it in GitHub Desktop.
Save lupyuen/1405685d6f847ea5d4d6302b196bb05e to your computer and use it in GitHub Desktop.
NuttX Log Parser with PureScript. See https://github.com/lupyuen/nuttx-purescript-parser
-- From https://github.com/lupyuen/nuttx-purescript-parser/blob/main/src/Main.purs
-- Parse the NuttX Exception and NuttX Stack Dump. Explain the NuttX Exception.
-- Based on https://github.com/purescript-contrib/purescript-string-parsers/blob/main/test/Examples.purs
-- Main Module will be started by `spago run`
module Main where
-- Import the Prelude (Common Functions for PureScript)
import Prelude hiding (between)
-- Import the PureScript Libraries
import Data.Either (Either(..))
import Data.Int (fromStringAs, hexadecimal)
import Data.Maybe (Maybe(..), fromMaybe, isJust)
import Data.String (stripPrefix)
import Data.String.Pattern (Pattern(..))
import Data.String.Regex (match)
import Data.String.Regex.Flags (noFlags)
import Data.String.Regex.Unsafe (unsafeRegex)
import Effect (Effect)
import Effect.Console (log, logShow)
import StringParser (Parser, optional, regex, runParser, skipSpaces, string)
import TryPureScript (render, withConsole)
-- Main Function that will run our Test Code.
-- `Effect` says that it will do Side Effects (printing to console)
-- `Unit` means that no value will be returned
-- The next line declares the Function Type. We can actually erase it, VSCode PureScript Extension will helpfully suggest it for us.
main :: Effect Unit
main = render =<< withConsole do
printResults -- Run our Test Code and print the results
-- Given this NuttX Exception: `riscv_exception: EXCEPTION: Load page fault. MCAUSE: 000000000000000d, EPC: 000000008000a0e4, MTVAL: 0000000880203b88`
-- Explain in friendly words: "We hit a Load Page Fault. Our code at Code Address 8000a0e4 tried to access the Data Address 0000000880203b88, which is Invalid."
-- The next line declares the Function Type. We can actually erase it, VSCode PureScript Extension will helpfully suggest it for us.
explainException ∷
Int -- MCAUSE: Cause of Exception
→ String -- EPC: Exception Program Counter
→ String -- MTVAL: Exception Value
→ String -- Returns the Exception Explanation
-- Explain the NuttX Exception with mcause 13
-- `<>` will concat 2 strings
-- "🎵 I never promised you a rose garden"
explainException 13 epc mtval =
"We hit a Load Page Fault."
<> " Our code at Code Address " <> epc
<> " tried to access the Data Address " <> mtval <> ", which is Invalid."
-- Explain the NuttX Exception with mcause 12
-- `<>` will concat 2 strings
explainException 12 epc mtval =
"Instruction Page Fault at " <> epc <> ", " <> mtval
-- Explain the Other NuttX Exceptions, that are not matched with the above
explainException mcause epc mtval =
"Unknown Exception: mcause=" <> show mcause <> ", epc=" <> epc <> ", mtval=" <> mtval
-- Given an Address, identify the Origin (NuttX Kernel or App) and Type (Code / Data / BSS / Heap)
identifyAddress ∷
String -- Address: `502198ac`
→ Maybe -- If Unknown Address: Return `Nothing`
{ -- Else return...
origin ∷ String -- Origin: `nuttx` or `qjs`
, type ∷ AddressType -- Type: Code / Data / BSS / Heap
}
-- Address 502xxxxx comes from NuttX Kernel Code
-- Address 800xxxxx comes from NuttX App Code (QuickJS)
-- `|` works like `if ... else if`
-- "a `matches` b" is same as "(matches a b)"
-- `Just` returns an OK Value. `Nothing` returns No Value.
identifyAddress addr
| "502....." `matches` addr = Just { origin: "nuttx", type: Code }
| "800....." `matches` addr = Just { origin: "qjs", type: Code }
| otherwise = Nothing -- Unknown Address
-- Address can point to Code, Data, BSS or Heap
data AddressType = Code | Data | BSS | Heap
-- How to display an Address Type
instance Show AddressType where
show Code = "Code"
show Data = "Data"
show BSS = "BSS"
show Heap = "Heap"
-- Return True if the Address matches the Regex Pattern.
-- Pattern is assumed to match the Entire Address.
matches ∷
String -- Pattern: `502.....`
→ String -- Address: `502198ac`
→ Boolean -- Return True if Address matches Regex `^502.....$`
-- Match the Begin `^` and End `$` of the Address
-- `<>` will concat 2 strings
-- "a `unsafeRegex` b" is same as "(unsafeRegex a b)"
matches pattern addr =
let
patternWrap = "^" <> pattern <> "$" -- Regex Pattern becomes `^502.....$`
in
isJust $ -- Is there a Match...
patternWrap `unsafeRegex` noFlags -- For our Regex Pattern (no special flags)
`match` addr -- Against the Address?
-- Test our code. Parse the NuttX Exception and NuttX Stack Dump. Explain the NuttX Exception.
-- `Effect` says that it will do Side Effects (printing to console)
-- `Unit` means that no value will be returned
-- The next line declares the Function Type. We can actually erase it, VSCode PureScript Extension will helpfully suggest it for us.
printResults :: Effect Unit
printResults = do
-- Explain the NuttX Exception.
-- `$ something something` is shortcut for `( something something )`
log "explainException 13 \"8000a0e4\" \"0000000880203b88\" is..."
log $ explainException 13 "8000a0e4" "0000000880203b88" -- We hit a Load Page Fault. Our code at Code Address 8000a0e4 tried to access the Data Address 0000000880203b88, which is Invalid.
log "-----"
log "explainException 12 \"epc\" \"mtval\" is..."
log $ explainException 12 "epc" "mtval" -- Instruction Page Fault at epc, mtval
log "-----"
log "explainException 0 \"epc\" \"mtval\" is..."
log $ explainException 0 "epc" "mtval" -- Unknown Exception: mcause=0, epc=epc, mtval=mtval
log "-----"
-- NuttX Kernel: 0x5020_0000 to 0x5021_98ac
-- NuttX App (qjs): 0x8000_0000 to 0x8006_4a28
log "identifyAddress \"502198ac\" is..."
logShow $ identifyAddress "502198ac" -- (Just { origin: "nuttx", type: Code })
log "-----"
log "identifyAddress \"8000a0e4\" is..."
logShow $ identifyAddress "8000a0e4" -- (Just { origin: "qjs", type: Code })
log "-----"
log "identifyAddress \"0000000800203b88\" is ..."
logShow $ identifyAddress "0000000800203b88" -- Nothing
log "-----"
-- Parse the NuttX Exception
-- Result: { epc: "8000ad8a", exception: "Instruction page fault", mcause: 12, mtval: "8000ad8a" }
doRunParser "parseException" parseException
"riscv_exception: EXCEPTION: Instruction page fault. MCAUSE: 000000000000000c, EPC: 000000008000ad8a, MTVAL: 000000008000ad8a"
-- Parse the line of NuttX Stack Dump
-- Result: { addr: "c02027e0", v1: "c0202010", v2: "00000000", v3: "00000001", v4: "00000000", v5: "00000000", v6: "00000000", v7: "8000ad8a", v8: "00000000" }
doRunParser "parseStackDump" parseStackDump
"stack_dump: 0xc02027e0: c0202010 00000000 00000001 00000000 00000000 00000000 8000ad8a 00000000"
-- Parse the line of NuttX Stack Dump with Timestamp
-- Result: { addr: "c02027e0", v1: "c0202010", v2: "00000000", v3: "00000001", v4: "00000000", v5: "00000000", v6: "00000000", v7: "8000ad8a", v8: "00000000" }
doRunParser "parseStackDump" parseStackDump
"[ 6.242000] stack_dump: 0xc02027e0: c0202010 00000000 00000001 00000000 00000000 00000000 8000ad8a 00000000"
-- Parse the NuttX Exception.
-- Given this NuttX Exception: `riscv_exception: EXCEPTION: Instruction page fault. MCAUSE: 000000000000000c, EPC: 000000008000ad8a, MTVAL: 000000008000ad8a`
-- Result: { epc: "8000ad8a", exception: "Instruction page fault", mcause: 12, mtval: "8000ad8a" }
-- The next line declares the Function Type. We can actually erase it, VSCode PureScript Extension will helpfully suggest it for us.
parseException ∷ Parser -- We're creating a Parser...
{ -- That accepts a String and returns...
exception ∷ String -- Exception Text: `Instruction page fault`
, mcause :: Int -- MCAUSE: 12
, epc :: String -- EPC: `8000ad8a`
, mtval :: String -- MTVAL: `8000ad8a`
}
-- To parse the line: `riscv_exception: EXCEPTION: Instruction page fault. MCAUSE: 000000000000000c, EPC: 000000008000ad8a, MTVAL: 000000008000ad8a`
parseException = do
-- Skip `riscv_exception: EXCEPTION: `
-- `void` means ignore the Text Captured
-- `$ something something` is shortcut for `( something something )`
-- `<*` is the Delimiter between Patterns
void $
string "riscv_exception:" -- Match the string `riscv_exception:`
<* skipSpaces -- Skip the following spaces
<* string "EXCEPTION:" -- Match the string `EXCEPTION:`
<* skipSpaces -- Skip the following spaces
-- `exception` becomes `Instruction page fault`
-- `<*` says when we should stop the Text Capture
exception <- regex "[^.]+"
<* string "."
<* skipSpaces
-- Skip `MCAUSE: `
-- `void` means ignore the Text Captured
-- `$ something something` is shortcut for `( something something )`
-- `<*` is the Delimiter between Patterns
void $ string "MCAUSE:" <* skipSpaces
-- `mcauseStr` becomes `000000000000000c`
-- We'll convert to integer later
mcauseStr <- regex "[0-9a-f]+" <* string "," <* skipSpaces
-- Skip `EPC: `
-- `epcWithPrefix` becomes `000000008000ad8a`
-- We'll strip the prefix `00000000` later
void $ string "EPC:" <* skipSpaces
epcWithPrefix <- regex "[0-9a-f]+" <* string "," <* skipSpaces
-- Skip `MTVAL: `
-- `mtvalWithPrefix` becomes `000000008000ad8a`
-- We'll strip the prefix `00000000` later
void $ string "MTVAL:" <* skipSpaces
mtvalWithPrefix <- regex "[0-9a-f]+"
-- Return the parsed content
-- `pure` because we're in a `do` block that allows (Side) Effects
-- TODO: Return a ParseError instead of -1
pure
{
exception
, mcause:
-1 `fromMaybe` -- If `mcauseStr` is not a valid hex, return -1
fromStringAs hexadecimal mcauseStr -- Else return the hex value of `mcauseStr`
, epc:
epcWithPrefix `fromMaybe` -- If `epcWithPrefix` does not have prefix `00000000`, return it
stripPrefix (Pattern "00000000") epcWithPrefix -- Else strip prefix `00000000` from `epc`
, mtval:
mtvalWithPrefix `fromMaybe` -- If `mtvalWithPrefix` does not have prefix `00000000`, return it
stripPrefix (Pattern "00000000") mtvalWithPrefix -- Else strip prefix `00000000` from `mtval`
}
-- Parse a line of NuttX Stack Dump.
-- Given this line of NuttX Stack Dump: `stack_dump: 0xc02027e0: c0202010 00000000 00000001 00000000 00000000 00000000 8000ad8a 00000000`
-- Result: { addr: "c02027e0", timestamp: "6.242000", v1: "c0202010", v2: "00000000", v3: "00000001", v4: "00000000", v5: "00000000", v6: "00000000", v7: "8000ad8a", v8: "00000000" }
-- The next line declares the Function Type. We can actually erase it, VSCode PureScript Extension will helpfully suggest it for us.
parseStackDump ∷ Parser -- We're creating a Parser...
{ -- That accepts a String and returns...
addr ∷ String -- Address: `c02027e0`
, v1 ∷ String -- Value 1: `c0202010`
, v2 ∷ String -- Value 2: `00000000`
, v3 ∷ String -- Value 3: `00000001`
, v4 ∷ String -- Value 4: `00000000`
, v5 ∷ String -- Value 5: `00000000`
, v6 ∷ String -- Value 6: `00000000`
, v7 ∷ String -- Value 7: `8000ad8a`
, v8 ∷ String -- Value 8: `00000000`
}
-- To parse the line: `stack_dump: 0xc02027e0: c0202010 00000000 00000001 00000000 00000000 00000000 8000ad8a 00000000`
parseStackDump = do
-- If the line begins with a Timestamp: `[ 6.242000] `
-- Skip `[ `
-- `optional` means Timestamp may or may not appear
-- `$ something something` is shortcut for `( something something )`
-- `<*` is the Delimiter between Patterns
optional $ -- Timestamp may or may not appear
string "[" -- Match the string `[`
<* skipSpaces -- Skip the following spaces
<* regex "[.0-9]+" -- Skip the number
<* string "]" -- Match the string `]`
<* skipSpaces -- Skip the following spaces
-- Skip `stack_dump: `
-- `void` means ignore the Text Captured
-- `$ something something` is shortcut for `( something something )`
-- `<*` is the Delimiter between Patterns
void $ string "stack_dump:" <* skipSpaces
-- `addr` becomes `c02027e0`
void $ string "0x"
addr <- regex "[0-9a-f]+" <* string ":" <* skipSpaces
-- `v1` becomes `c0202010`
-- `v2` becomes `00000000` and so on
v1 <- regex "[0-9a-f]+" <* skipSpaces
v2 <- regex "[0-9a-f]+" <* skipSpaces
v3 <- regex "[0-9a-f]+" <* skipSpaces
v4 <- regex "[0-9a-f]+" <* skipSpaces
v5 <- regex "[0-9a-f]+" <* skipSpaces
v6 <- regex "[0-9a-f]+" <* skipSpaces
v7 <- regex "[0-9a-f]+" <* skipSpaces
v8 <- regex "[0-9a-f]+" <* skipSpaces
-- Return the parsed content
-- `pure` because we're in a `do` block that allows (Side) Effects
pure
{ addr
, v1
, v2
, v3
, v4
, v5
, v6
, v7
, v8
}
-- Shows the results of calling `runParser`. We typically don't want to use
-- this function when writing a parser because it doesn't help us debug
-- our code when we write it incorrectly.
doRunParser :: forall a. Show a => String -> Parser a -> String -> Effect Unit
doRunParser parserName parser content = do
log $ "(runParser) Parsing content with '" <> parserName <> "'"
case runParser parser content of
Left error -> logShow error
Right result -> log $ "Result: " <> show result
log "-----"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment