Debugging Monkeys on a Raspberry Pi


The last couple times I wrote about using a RaspBerry Pi and Windows IoT:

https://blogs.msdn.microsoft.com/calvin_hsia/2017/03/16/getting-started-with-windows-internet-of-things-windows-iot/

https://blogs.msdn.microsoft.com/calvin_hsia/2017/04/29/create-a-security-system-with-cameras-on-your-windows-iot-raspberry-pi/

I was contemplating getting Monkeys working on an Arduino, but that would have required a lot more effort (I’d have to implement a lot of the base functionality, like collection classes, hash tables, I/O, etc.) and it would not have enough memory to generate the text. The Pi has more memory and using the .Net framework dictionary and async support made the task a breeze.

Below is some sample code showing how we can get the Shakespearean monkeys working on the Raspberry Pi. Note that the code also opens a General Purpose I/O pin (if available on the current platform). It then toggles the pin high and low in the timer. So you can connect, e.g. an LED to Pin 5 and see it flash as the code runs.

The difference in the heart of the C# algorithm is pretty small. The original program was a Console app, and the IoT version is a Universal Windows App, which can run on an Xbox, IoT, Windows, HoloLens, and Windows Phone.

You can set up your environment to be Local Machine (x86 (32 bit), x64 (64 bit)), or remote machine ARM (the Raspberry Pi), or Device (like a USB connected Windows Phone).

Once you set up your environment, the debug experience is essentially the same for each platform. You can set breakpoints, tracepoints, view disassembly, single step, SetNextStatement, examine values, etc.

You can even view the machine registers and single step the Jitted code. Hit Ctrl-F11 (show disassembly), right click and Show Code Bytes.

Here’s a view at a breakpoint for ARM, showing the Raspberry PI register values, the assembly code and the machine code. The memory window is showing the executable code at the current PC (Program Counter) 0x05ADF0AC. You can twiddle the register values and even the memory contents

clip_image001

Changing the platform to x64 (Local machine) you can see that the registers are 64 bits wide and the machine

clip_image002

And here’s the same for x86:

clip_image003

Seeing the Jitted code and how it differs hugely depending on the platform makes one appreciate the work done under the covers to make the same code run identically on multiple platforms. Also, the debugger knows how to disassemble the binary to asm code for each platform.

Btw, it’s very useful to remember that 0x90 is a NOP or No Operation for x86 (and, interestingly, x64). When debugging a scenario and I want to comment out some code, I just replace each of the code bytes in the Memory Window with 0x90 so it won’t be executed. This is much faster than stopping debugging, rebuilding, redeploying, replaying the debug scenario to the breakpoint. (Of course, Edit And Continue is similarly very helpful, but not always available) Similarly, a 0xCC is an Int 3, which is the equivalent of a DebugBreak(), which causes a breakpoint.

Try it yourself: add the line nChars = 77; Just before the line, replace the code bytes with 0x90. This works for both x86 and x64. It’s handy to have a single byte NOP instruction. I couldn’t find one for the ARM, which uses MOV R0, R0 (0xe1a00000). Then single step through the NOPs you just replaced and notice that the added line is not even executed (nChars stays as 0).

Another debugger feature I like for managed code is the Make Object Id. Right click on a value in the Watch or Locals windows and choose Make Object ID. That particular instance will now be displayed with the suffix {$1}, {$2}, etc. If you have many instances of the same type, this is very handy to distinguish each instance. Managed code needs this because the object’s address can change through Garbage Collection.

BTW, I can’t emphasize this enough: the Set Next Statement command (Ctrl Shift F10) is extremely powerful: you can change the next line of code that will be executed. So you can re-execute or skip code easily. Combined with twiddling variable values, this is a big time saver.

Software Development is a cycle of:

  1. Write code
  2. Deploy (could be automatic)
  3. Debug code
  4. Change code
  5. Repeat as necessary

Of course my favorite development time saver is not debugging at all: use Test Driven Development, where you run a test to execute your code and observe the results. Then you can use released build optimized code most of the time, run tests while coding, verify you didn’t break something, etc.

  1. Write test and code simultaneously
  2. Run test
  3. Change code
  4. Repeat as necessary

Download WarAndPeace.txt and add it to the VS Solution. Change it’s “Copy to OutputDirectory” Property to “Copy if newer”. This will deploy the file with the application. Or, you can use the built in Hamlet Act III.

WarAndPeace.txt

<code>

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.Devices.Gpio;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
 
 
// Start Visual Studio
// File->New->Project->C#->Blank App (Universal Windows)
// Name it "MonkeysUWP"
// https://blogs.msdn.microsoft.com/calvin_hsia/2017/01/31/faster-monkeys/
 
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
 
namespace MonkeysUWP
{
  /// <summary>
  /// An empty page that can be used on its own or navigated to within a Frame.
  /// </summary>
  public sealed partial class MainPage : Page
  {
    static string txtHamlet =
@"To be, or not to be--that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune
Or to take arms against a sea of troubles
And by opposing end them. To die, to sleep--
No more--and by a sleep to say we end
The heartache, and the thousand natural shocks
That flesh is heir to. 'Tis a consummation
Devoutly to be wished. To die, to sleep--
To sleep--perchance to dream: ay, there's the rub,
For in that sleep of death what dreams may come
When we have shuffled off this mortal coil,
Must give us pause. There's the respect
That makes calamity of so long life.
For who would bear the whips and scorns of time,
Th' oppressor's wrong, the proud man's contumely
The pangs of despised love, the law's delay,
The insolence of office, and the spurns
That patient merit of th' unworthy takes,
When he himself might his quietus make
With a bare bodkin? Who would fardels bear,
To grunt and sweat under a weary life,
But that the dread of something after death,
The undiscovered country, from whose bourn
No traveller returns, puzzles the will,
And makes us rather bear those ills we have
Than fly to others that we know not of?
Thus conscience does make cowards of us all,
And thus the native hue of resolution
Is sicklied o'er with the pale cast of thought,
And enterprise of great pitch and moment
With this regard their currents turn awry
And lose the name of action. -- Soft you now,
The fair Ophelia! -- Nymph, in thy orisons
Be all my sins remembered.
";
    public MainPage()
    {
      this.InitializeComponent();
      this.Loaded += MainPage_Loaded;
    }
    private const int LED_PIN = 5;
    private GpioPin _pin;
    private GpioPinValue _pinValue;
    private DispatcherTimer _timer;
 
    // fast way: use a dictionary of dictionaries
    /// <summary>
    /// An instance of cPriorData for each sequence of letters with length = PatternLength -1
    /// </summary>
    public class cPriorData
    {
      // the # of occurrences of the prior sequence.
      public int cnt;
      // for each single char "a", "b", how many follow the sequence
      // e.g. for text "to be or not to be", and prior = "no",
      // this dict will have one entry 't' == 1
      public Dictionary<charint> _dictChars = new Dictionary<charint>();
      public override string ToString()
      {
        return $"{cnt}";
      }
    }
 
    Dictionary<stringcPriorData> _dictCPriorData = new Dictionary<stringcPriorData>();
 
    Random _rand = new Random(1);
    int _maxTbLen = 10000;
    int _PatternLength = 8;
    string _txtSourceToImitate = txtHamlet;
    string _strPrior;
    TextBlock _tb;
    ScrollViewer _vwr;
    CancellationTokenSource _cancSource = new CancellationTokenSource();
    object _timerLock = new object();
 
    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
      try
      {
        var warandPeaceFile = "WarAndPeace.txt";
        if (System.IO.File.Exists(warandPeaceFile))
        {
          _txtSourceToImitate = System.IO.File.ReadAllText(warandPeaceFile);
        }
        var relPanel = new RelativePanel();
        var spCtrls = new StackPanel()
        {
          Orientation = Orientation.Horizontal
        };
        relPanel.Children.Add(spCtrls);
        var sldPatLen = new Slider()
        {
          Minimum = 2,
          Maximum = 20,
          TickFrequency = 2,
          IsThumbToolTipEnabled = true,
          Width = 500,
          Value = _PatternLength,
          Header = "Pattern Length"
        };
        spCtrls.Children.Add(sldPatLen);
        var tbPatLen = new TextBlock()
        {
          Width = 100,
          Text = _PatternLength.ToString()
        };
        spCtrls.Children.Add(tbPatLen);
        var chkPause = new CheckBox()
        {
          Content = "Pause"
        };
        spCtrls.Children.Add(chkPause);
        var btnQuit = new Button()
        {
          Content = "Quit",
          Margin = new Thickness(10, 0, 0, 0)
        };
        spCtrls.Children.Add(btnQuit);
        btnQuit.Click += (oq, eq) =>
        {
          Application.Current.Exit();
        };
        _vwr = new ScrollViewer();
        _tb = new TextBlock()
        {
          TextWrapping = TextWrapping.Wrap,
        };
        RelativePanel.SetBelow(_vwr, spCtrls);
        _tb.Text = string.Empty;
        _vwr.Content = _tb;
        relPanel.Children.Add(_vwr);
        this.Content = relPanel;
        await addTextAsync($"Shakespearean Monkeys, a la Tolstoy\n");
        // if this device (Raspberrry PI?) has general purpose IO pins,
        // lets flash an LED
        var gpio = GpioController.GetDefault();
        if (gpio != null)
        {
          GpioOpenStatus openStatus;
          if (gpio.TryOpenPin(LED_PIN, GpioSharingMode.Exclusive, out _pin, openStatus: out openStatus))
          {
            _pinValue = GpioPinValue.High;
            _pin.Write(_pinValue);
            _pin.SetDriveMode(GpioPinDriveMode.Output);
            await addTextAsync($"GPIO Pin {LED_PIN} opened succesfully: {openStatus}\n");
          }
          else
          {
            await addTextAsync($"Couldn't open IO Pin {LED_PIN} : {openStatus}\n");
          }
        }
        else
        {
          await addTextAsync($"NO GPIO Controller found\n");
        }
 
        await FillDictionaryTextAsync();
        _timer = new DispatcherTimer();
        _timer.Interval = TimeSpan.FromMilliseconds(100);
        _strPrior = string.Empty;
        _timer.Tick += async (ot, et) =>
        {
          if (Monitor.TryEnter(_timerLock))
          {
            try
            {
              var newTxt = GenerateText(100);
              await addTextAsync(newTxt);
              if (_pin != null)
              {
                if (_pinValue == GpioPinValue.High)
                {
                  _pinValue = GpioPinValue.Low;
                  _pin.Write(_pinValue);
                }
                else
                {
                  _pinValue = GpioPinValue.High;
                  _pin.Write(_pinValue);
                }
              }
              if (_cancSource.IsCancellationRequested)
              {
                _timer.Stop();
              }
            }
            finally
            {
              Monitor.Exit(_timerLock);
            }
          }
        };
        _timer.Start();
        chkPause.Click += (oc, ec) =>
         {
           if (chkPause.IsChecked.Value)
           {
             _timer.Stop();
                     //                         DumpDictionary();
                   }
           else
           {
             _timer.Start();
           }
         };
        this.Unloaded += (ou, eu) =>
        {
          if (_pin != null)
          {
            _pin.Dispose();
          }
          _dictCPriorData.Clear();
        };
        sldPatLen.ValueChanged += async (os, es) =>
        {
          if (!_cancSource.IsCancellationRequested)
          {
            var newval = (int)sldPatLen.Value;
                    //                        if (int.TryParse(tbPatLen.Text, out newval))
                    {
              if (newval > 1 && _PatternLength != newval)
              {
                _timer.Stop();
                _cancSource.Cancel();
                await addTextAsync($"\nChanging pattern length from {_PatternLength} to {newval}\n");
                try
                {
                  Monitor.Enter(_timerLock);
                  chkPause.IsChecked = false;
                  _PatternLength = newval;
                  tbPatLen.Text = newval.ToString();
                  await FillDictionaryTextAsync();
                  _timer.Start();
                }
                finally
                {
                  _cancSource.Dispose();
                  _cancSource = new CancellationTokenSource();
                  Monitor.Exit(_timerLock);
                }
              }
            }
          }
 
        };
      }
      catch (Exception ex)
      {
        this.Content = new TextBlock() { Text = ex.ToString() };
      }
    }
    private async Task addTextAsync(string str)
    {
      await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
          () =>
          {
            if (_tb.Text.Length > _maxTbLen)
            {
              _tb.Text = _tb.Text.Substring(_maxTbLen - 100);
            }
            _tb.Text += str;
            _vwr.ChangeView(0, 10000, null);
          });
    }
 
    private async Task FillDictionaryTextAsync()
    {
      await addTextAsync($"\nFilling Dictionary PatLen = {_PatternLength}  TxtLen = {_txtSourceToImitate.Length:n0}\n");
      _dictCPriorData.Clear();
      _strPrior = " ";
      for (int i = 0; i < _txtSourceToImitate.Length; i++)
      {
        if ((_txtSourceToImitate.Length - i) % 100000 == 0)
        {
          await addTextAsync($" {(_txtSourceToImitate.Length - i) / 100000}");
        }
        // read a character from the text
        var chrNew = _txtSourceToImitate[i];
        if (_strPrior.Length < _PatternLength)
        {
          _strPrior += chrNew;
          continue;
        }
        // we now have prior with length = patlen -1 and a new character
        cPriorData pData = null;
        // have we encountered this strPrior before?
        if (!_dictCPriorData.TryGetValue(_strPrior, out pData))
        {
          pData = new cPriorData();
          _dictCPriorData[_strPrior] = pData;
        }
        // increment the # of times we've seen it
        pData.cnt += 1;
        // for this strPrior, have we seen the chrNew before?
        if (pData._dictChars.ContainsKey(chrNew))
        {
          pData._dictChars[chrNew] += 1;
        }
        else
        {
          pData._dictChars[chrNew] = 1;
        }
        if (_strPrior.Length > 0)
        {
          // remove 1st char
          _strPrior = _strPrior.Substring(1);
        }
        _strPrior += chrNew;
      }
    }
    private async Task DumpDictionaryAsync()
    {
      await addTextAsync($"\nPatLen = {_PatternLength}\n");
      int n = 0;
      foreach (var prior in _dictCPriorData)
      {
        await addTextAsync($"{n++,5} '{prior.Key}{prior.Value.cnt}");
        foreach (var p in prior.Value._dictChars)
        {
          await addTextAsync($" ('{p.Key}'  {p.Value})");
        }
        await addTextAsync("\n");
      }
    }
    private string GenerateText(int nLenToGenerate)
    {
      int nChars;
      var strGenerated = new System.Text.StringBuilder();
      for (nChars = 0; nChars < nLenToGenerate; nChars++)
      {
        if (_cancSource.IsCancellationRequested)
        {
          break;
        }
        if (string.IsNullOrEmpty(_strPrior))
        {
          // we need some sort of initial prior string
          // so we'll take it from a random entry in the data
          int numSkip = _rand.Next(Math.Min(100, _dictCPriorData.Count));
          if (_dictCPriorData.Count < 30)
          {
            // when testing with very few items
            numSkip = 0;
          }
          _strPrior = _dictCPriorData
              .Skip(numSkip)
              // look for one that is preceded by " "
              .SkipWhile(
                  d => !d.Key.StartsWith(" "))
              .First()
              .Key;
          // we need to print the new prior string	
          // indicates it's a restart by using Upper case
          strGenerated.Append(_strPrior.ToUpper());
        }
        cPriorData pData = null;
        if (!_dictCPriorData.TryGetValue(_strPrior, out pData))
        {
          // for some reason, the sequence we have isn't found. 
          // could be that the last sequence in the input text is unique
          // and because it's the end of the text, it's not in the data
          // reset to new random start
          _strPrior = string.Empty;
          continue;
        }
        int targetRand = _rand.Next(pData.cnt);
        var target = pData._dictChars
            // we subtract each entry's value until we reach 0
            .Where(d => (targetRand -= d.Value) < 0)
            .FirstOrDefault();
        // remove 1st char from the strPrior and add the target char at the end
        _strPrior = _strPrior.Substring(1) + target.Key;
        strGenerated.Append(target.Key.ToString());
      }
      return strGenerated.ToString();
    }
  }
}

</code>

Comments (0)

Skip to main content