Skip to content

Instantly share code, notes, and snippets.

@azyobuzin
Last active October 16, 2016 05:56
Show Gist options
  • Save azyobuzin/db9954d47c83bc99e9ad37070ab84a18 to your computer and use it in GitHub Desktop.
Save azyobuzin/db9954d47c83bc99e9ad37070ab84a18 to your computer and use it in GitHub Desktop.
ソートの様子を観察するやつ
*.suo
*.user
*.userprefs
bin/
obj/
.vs/
packages/
#include <stdio.h>
#define NUM 5
int main(void) {
int test[NUM];
int tmp;
int i, j, s, t;
printf("%d人の点数を入力してください。\n", NUM);
for(i=0; i<NUM; i++){
scanf("%d", &test[i]);
}
for(s=0; s<NUM-1; s++){
for(t=s+1; t<NUM; t++){
if(test[t] > test[s]){
tmp = test[t];
test[t] = test[s];
test[s] = tmp;
}
}
}
for(j=0; j<NUM; j++){
printf("%d番目の人の点数は%dです。\n", j+1, test[j]);
}
return 0;
}
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("DebugSample5")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{D72D4744-EF0E-41A5-A5C7-1D08C96798A8}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>DebugSample5</RootNamespace>
<AssemblyName>DebugSample5</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Parseq">
<HintPath>packages\Parseq.3.0.0.0\lib\portable-net40+sl5+win8+wp8+wpa81\Parseq.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
</ItemGroup>
<ItemGroup>
<Compile Include="GdbOutputParser.cs" />
<Compile Include="GdbSession.cs" />
<Compile Include="Program.cs" />
<Compile Include="GdbResultRecord.cs" />
<Compile Include="GdbAsyncRecord.cs" />
<Compile Include="Utils.cs" />
<Compile Include="AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="7_Sample5.c">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DebugSample5", "DebugSample5.csproj", "{D72D4744-EF0E-41A5-A5C7-1D08C96798A8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D72D4744-EF0E-41A5-A5C7-1D08C96798A8}.Debug|x86.ActiveCfg = Debug|x86
{D72D4744-EF0E-41A5-A5C7-1D08C96798A8}.Debug|x86.Build.0 = Debug|x86
{D72D4744-EF0E-41A5-A5C7-1D08C96798A8}.Release|x86.ActiveCfg = Release|x86
{D72D4744-EF0E-41A5-A5C7-1D08C96798A8}.Release|x86.Build.0 = Release|x86
EndGlobalSection
EndGlobal
namespace DebugSample5
{
public class GdbAsyncRecord
{
public int? Token { get; set; }
public AsyncRecordType Type { get; set; }
public AsyncClass Class { get; set; }
public dynamic Result { get; set; }
}
public enum AsyncClass
{
Stopped
}
public enum AsyncRecordType
{
Exec,
Status,
Notify
}
}
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.RegularExpressions;
using Parseq;
using Parseq.Combinators;
namespace DebugSample5
{
public static class GdbOutputParser
{
// https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Output-Syntax.html
private static readonly Parser<char, GdbResultRecord> s_resultRecord;
private static readonly Parser<char, GdbAsyncRecord> s_asyncRecord;
static GdbOutputParser()
{
var token = Chars.Char(c => c >= '0' && c <= '9').Many1()
.Select(x => int.Parse(string.Concat(x)));
var variable = Chars.Char(c => (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '-')
.Many1().Select(x => string.Concat(x));
var cstring = Chars.Sequence("\\\"").Or(Chars.Char(c => c != '"').Select(c => (IEnumerable<char>)new[] { c }))
.Many0()
.Between(Chars.Char('"').Ignore(), Chars.Char('"').Ignore())
.Select(x => Regex.Unescape(string.Concat(x.Select(y => string.Concat(y)))));
var className = Chars.Char(c => c >= 'a' && c <= 'z').Many1();
var resultClass = className.Select(ParseEnum<ResultClass>);
var asyncClass = className.Select(ParseEnum<AsyncClass>);
Parser<char, KeyValuePair<string, object>> _result = null;
var result = Combinator.Lazy(() => _result);
Parser<char, object> _value = null;
var value = Combinator.Lazy(() => _value);
var tuple = Combinator.SepBy0(result, Chars.Char(',').Ignore())
.Between(Chars.OneOf('{', '[').Ignore(), Chars.OneOf('}', ']').Ignore())
.Select(CreateExpandoObject);
var list = Combinator.SepBy0(value, Chars.Char(',').Ignore())
.Between(Chars.Char('[').Ignore(), Chars.Char(']').Ignore())
.Select(Enumerable.ToArray);
_value = Combinator.Choice(
cstring.Select(x => (object)x),
tuple,
list.Select(x => (object)x),
Parser.FailFast<char, object>("const | tuple | list を満たしません。")
);
_result = variable.Pipe(Chars.Char('='), _value, (k, _, v) => new KeyValuePair<string, object>(k, v));
s_resultRecord = token.Optional().Pipe(
Chars.Char('^'),
resultClass,
Chars.Char(',').Bindr(_result).Many0(),
(t, _, c, r) => new GdbResultRecord()
{
Token = t.Case(() => (int?)null, x => x),
Class = c,
Result = CreateExpandoObject(r)
});
var asyncOutput = asyncClass.Pipe(Chars.Char(',').Bindr(_result).Many0(), Tuple.Create);
s_asyncRecord = token.Optional().Pipe(
Combinator.Choice(
Chars.Char('*').Select(_ => AsyncRecordType.Exec),
Chars.Char('+').Select(_ => AsyncRecordType.Status),
Chars.Char('=').Select(_ => AsyncRecordType.Notify)
),
asyncOutput,
(t, a, o) => new GdbAsyncRecord()
{
Token = t.Case(() => (int?)null, x => x),
Type = a,
Class = o.Item1,
Result = CreateExpandoObject(o.Item2)
});
}
private static T ParseEnum<T>(IEnumerable<char> input)
{
return (T)Enum.Parse(typeof(T), string.Concat(input), true);
}
private static object CreateExpandoObject(IEnumerable<KeyValuePair<string, object>> results)
{
var eo = (IDictionary<string, object>)new ExpandoObject();
foreach (var x in results) eo.Add(x.Key, x.Value);
return eo;
}
private static T Parse<T>(Parser<char, T> parser, string input)
{
return parser.Run(input.AsStream()).Case(
(stream, msg) =>
{
var exMsg = stream.Current.Select(x => x.Item1).Case(() => msg, pos => $"{msg}\n{pos.Line}:{pos.Column}");
throw new FormatException(exMsg);
},
(stream, result) => result
);
}
public static GdbResultRecord ParseResultRecord(string input) => Parse(s_resultRecord, input);
public static GdbAsyncRecord ParseAsyncRecord(string input) => Parse(s_asyncRecord, input);
}
}
using System;
using System.Collections.Generic;
namespace DebugSample5
{
public class GdbResultRecord
{
public int? Token { get; set; }
public ResultClass Class { get; set; }
public dynamic Result { get; set; }
public GdbResultRecord Expect(ResultClass resultClass)
{
if (this.Class != resultClass)
{
object msg;
throw new InvalidOperationException(
((IDictionary<string, object>)this.Result).TryGetValue("msg", out msg)
? msg.ToString()
: $"Expected: {resultClass}, Actual: {this.Class}"
);
}
return this;
}
}
public enum ResultClass
{
None,
Done,
Running,
Connected,
Error,
Exit
}
}
// テスト用
//#define ECHO
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
namespace DebugSample5
{
public class GdbSession : IDisposable
{
private readonly Process _process;
private readonly StreamWriter _stdin;
private readonly StreamReader _stdout;
private int _token = 0;
private GdbSession(Process process)
{
_process = process;
_stdin = process.StandardInput;
_stdout = process.StandardOutput;
}
/// <summary>GDB/MI プロセスを起動</summary>
public static GdbSession Start()
{
var process = Process.Start(
new ProcessStartInfo("gdb", "--interpreter=mi")
{
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true
}
);
return new GdbSession(process);
}
public void SendCommand(string command)
{
#if ECHO
Console.WriteLine(command);
#endif
_stdin.WriteLine(command);
}
public IReadOnlyList<string> ReadRecords()
{
// 「(gdb)」まで読み取る
var records = new List<string>();
while (true)
{
var line = _stdout.ReadLine();
#if ECHO
Console.WriteLine(line);
#endif
if (line == null || line.StartsWith("(gdb)", StringComparison.Ordinal))
return records;
records.Add(line);
}
}
/// <summary>コマンドを送信して、結果を読み取る</summary>
public GdbResultRecord SendAndRead(string command)
{
var token = _token++;
// トークンを付けてコマンドを実行することで、戻り値を識別する
this.SendCommand($"{token:D}{command}");
var expected = $"{token:D}^";
foreach (var record in this.ReadRecords())
{
// それっぽいのがあればパースして返す
if (record.StartsWith(expected, StringComparison.Ordinal))
{
return GdbOutputParser.ParseResultRecord(record);
}
}
throw new InvalidOperationException("応答が返ってきませんでした。");
}
/// <summary>プロセスにアタッチ</summary>
public void AttachProcessOrThreadGroup(int id)
{
this.SendAndRead($"-target-attach {id:D}").Expect(ResultClass.Done);
}
/// <summary>シンボルファイルを読み込む</summary>
public void SymbolFile(string file)
{
this.SendAndRead("-file-symbol-file " + ToCString(file)).Expect(ResultClass.Done);
}
/// <summary>シンボル情報付き実行可能ファイルを読み込む</summary>
public void ExecAndSymbolsFile(string file)
{
this.SendAndRead("-file-exec-and-symbols " + ToCString(file)).Expect(ResultClass.Done);
}
/// <summary>ブレークポイントを設定</summary>
public int InsertBreak(string location)
{
var result = this.SendAndRead("-break-insert " + ToCString(location))
.Expect(ResultClass.Done)
.Result;
return Utils.ParseIntInvariant((string)result.bkpt.number);
}
/// <summary>実行再開</summary>
public void Continue()
{
this.SendAndRead("-exec-continue").Expect(ResultClass.Running);
}
/// <summary>最初から実行</summary>
public void Run()
{
this.SendAndRead("-exec-run").Expect(ResultClass.Running);
}
/// <summary>実行が停止した行を取得</summary>
public int ReadStoppedLine()
{
foreach (var record in this.ReadRecords())
{
if (record.StartsWith("*stopped", StringComparison.Ordinal))
{
var asyncRecord = GdbOutputParser.ParseAsyncRecord(record);
return Utils.ParseIntInvariant((string)asyncRecord.Result.frame.line);
}
}
throw new InvalidOperationException("停止通知を読み取れませんでした。");
}
/// <summary>次の命令まで実行</summary>
public void Next()
{
this.SendAndRead("-exec-next").Expect(ResultClass.Running);
}
/// <summary>ローカル変数を取得</summary>
public IReadOnlyList<KeyValuePair<string, string>> GetLocals()
{
var result = this.SendAndRead("-stack-list-locals 1")
.Expect(ResultClass.Done)
.Result;
var locals = (IEnumerable<dynamic>)result.locals;
return locals.Select(x => new KeyValuePair<string, string>((string)x.name, (string)x.value)).ToArray();
}
public void Dispose(bool disposing)
{
if (!_process.HasExited)
{
// gdb プロセスを終了させる
this.SendCommand("-gdb-exit");
_stdin.Close();
_stdout.Close();
if (!_process.WaitForExit(5000))
_process.Kill();
_process.Close();
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
~GdbSession()
{
this.Dispose(false);
}
private static string ToCString(string str)
{
var sb = new StringBuilder(str)
.Replace("\\", "\\\\")
.Replace("\a", "\\a")
.Replace("\b", "\\b")
.Replace("\f", "\\f")
.Replace("\n", "\\n")
.Replace("\r", "\\r")
.Replace("\t", "\\t")
.Replace("\v", "\\v")
.Replace("\"", "\\\"");
return "\"" + sb + "\"";
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Parseq" version="3.0.0.0" targetFramework="net45" />
</packages>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
namespace DebugSample5
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine("コンパイル中");
var cr = Compile();
Console.WriteLine("実行ファイル: " + cr.ExecutableFile);
var snapshots = Debug(cr);
// 実行ファイルの一時ディレクトリを削除
Directory.GetParent(cr.ExecutableFile).Delete(true);
foreach (var s in snapshots)
{
Console.WriteLine($"{s.Line} 行目");
foreach (var kvp in s.Locals)
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
Console.WriteLine();
}
Console.WriteLine("画像出力中");
CreateGifImage(snapshots);
}
private struct CompilationResult
{
public string SourceFile { get; set; }
public string ExecutableFile { get; set; }
}
/// <summary>gcc でサンプルプログラムをコンパイルする</summary>
private static CompilationResult Compile()
{
// ソースコードは、このアセンブリと同じディレクトリにコピーされる
var sourceFile = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "7_Sample5.c");
// 作業ディレクトリを確保
var workdir = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));
// gcc 実行
using (var gcc = Process.Start(new ProcessStartInfo("gcc", $"-std=c11 -g \"{sourceFile}\"")
{
WorkingDirectory = workdir.FullName
}))
{
gcc.WaitForExit();
if (gcc.ExitCode != 0)
throw new InvalidOperationException("コンパイル失敗");
}
return new CompilationResult()
{
SourceFile = sourceFile,
ExecutableFile = Path.Combine(workdir.FullName, "a.out")
};
}
private struct LocalsSnapshot
{
public int Line { get; set; }
public IReadOnlyDictionary<string, string> Locals { get; set; }
}
/// <summary>デバッグ実行をし、ループ中の変数の状態を取得</summary>
private static IReadOnlyList<LocalsSnapshot> Debug(CompilationResult cr)
{
// gdb とコンパイルされた実行ファイルを起動
// -file-exec-and-symbols を使うと、デバッグ対象の標準入出力までリダイレクトされていまうので
// 自分でプロセスを立ち上げることに(root権限が必要)
using (var gdb = GdbSession.Start())
using (var targetProcess = Process.Start(cr.ExecutableFile))
{
gdb.ReadRecords(); // 最初の情報は無視
// シンボル読み込み
gdb.SymbolFile(cr.ExecutableFile);
// アタッチ
gdb.AttachProcessOrThreadGroup(targetProcess.Id);
// 14行目にブレークポイント
gdb.InsertBreak($"{cr.SourceFile}:14");
// 14行目まで実行
gdb.Continue();
var result = new List<LocalsSnapshot>();
while (true)
{
var line = gdb.ReadStoppedLine();
// 止まった行と変数の状態を記録
result.Add(new LocalsSnapshot()
{
Line = line,
Locals = gdb.GetLocals().ToDictionary(x => x.Key, x => x.Value)
});
// 23 行目以降なら、ループを越えたのでデバッグ終了
if (line >= 23) break;
gdb.Next(); // 次の行へ
}
return result;
}
}
/// <summary>変数の変化の状況をアニメーション GIF として保存</summary>
private static void CreateGifImage(IReadOnlyList<LocalsSnapshot> snapshots)
{
// Mono の System.Drawing の GIF はよろしくなさそうなので、 PNG 出力からの ImageMagick
// 大量の PNG を吐き出したいのでディレクトリを作る
var tmpDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));
try
{
for (var i = 0; i < snapshots.Count; i++)
{
using (var img = CreateImage(
i > 0 ? snapshots[i - 1] : (LocalsSnapshot?)null,
snapshots[i],
i + 1,
snapshots.Count
))
{
img.Save(Path.Combine(tmpDirectory.FullName, $"{i:D4}.png"));
}
}
// ImageMagick に食わせる
using (var convert = Process.Start("convert", $"-delay 50 -loop 0 \"{tmpDirectory.FullName}/*.png\" result.gif"))
convert.WaitForExit();
}
finally
{
tmpDirectory.Delete(true); // 何があっても消す
}
}
private static readonly FontFamily s_fontFamily = FontFamily.Families
.FirstOrDefault(x => x.Name == "DejaVu Sans Mono") ?? FontFamily.GenericMonospace;
/// <summary>1フレームを作成</summary>
/// <param name="prev">1つ前のスナップショット(比較するため)</param>
/// <param name="current">このフレームに使うスナップショット</param>
private static Image CreateImage(LocalsSnapshot? prev, LocalsSnapshot current, int currentIndex, int total)
{
const int num = 5; // Sample5.c の NUM
const int marginTop = 25; // 行数表示用に空けておく高さ
const int marginBottom = 5; // 枠と矢印の間
const int width = 60; // 枠1つあたりの幅
const int height = 30; // 枠1つあたりの高さ
var bmp = new Bitmap(width * (num + 1), 90);
using (var g = Graphics.FromImage(bmp))
using (var stFont = new Font(s_fontFamily, 12))
{
// 背景塗りつぶし
g.FillRectangle(Brushes.White, new Rectangle(new Point(0, 0), bmp.Size));
g.DrawString($"[{currentIndex}/{total}] Line {current.Line}", stFont, Brushes.Black, 0, 0);
// 枠線を描く
for (var i = 0; i < num; i++)
g.DrawRectangle(Pens.Silver, width * i, marginTop, width, height);
var prevTest = prev.HasValue ? ParseGdbIntArray(prev.Value.Locals["test"]) : null;
var currentTest = ParseGdbIntArray(current.Locals["test"]);
// test の中身を描く
using (var testFont = new Font(s_fontFamily, 20))
{
for (var i = 0; i < num; i++)
{
var isChanged = prevTest != null && prevTest[i] != currentTest[i];
var strToDraw = currentTest[i].ToString();
var strSize = g.MeasureString(strToDraw, testFont);
g.DrawString(
strToDraw, testFont,
isChanged ? Brushes.Red : Brushes.Black, // 変更があったら赤にする
width * i + (width - strSize.Width) / 2.0f,
marginTop + (height - strSize.Height) / 2.0f
);
}
}
// s
using (var sPen = new Pen(Color.Blue, 2))
{
var s = Utils.ParseIntInvariant(current.Locals["s"]);
DrawArrowAndText(g, stFont, sPen, new PointF(width * s + width / 2.0f, marginTop + height + marginBottom), $"s={s}");
}
// t
using (var tPen = new Pen(Color.Green, 2))
{
var t = Utils.ParseIntInvariant(current.Locals["t"]);
DrawArrowAndText(g, stFont, tPen, new PointF(width * t + width / 2.0f, marginTop + height + marginBottom), $"t={t}");
}
}
return bmp;
}
/// <summary>{1, 2, 3, ...} 形式の表現から配列を作成</summary>
private static int[] ParseGdbIntArray(string s)
{
return Array.ConvertAll(
s.Substring(1, s.Length - 2) // { と } を取り除く
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
Utils.ParseIntInvariant
);
}
/// <summary>上向き矢印を描き、その下に文字を描く</summary>
private static void DrawArrowAndText(Graphics g, Font font, Pen pen, PointF center, string str)
{
const float w = 6; // 矢印の幅の半分
const float h = 6; // 矢印の高さの差
const float len = 12; // 棒の長さ
// 左側
g.DrawLine(pen, center.X, center.Y, center.X - w, center.Y + h);
// 右側
g.DrawLine(pen, center.X, center.Y, center.X + w, center.Y + h);
// 棒
g.DrawLine(pen, center.X, center.Y, center.X, center.Y + len);
// 文字
var strWidth = g.MeasureString(str, font).Width;
g.DrawString(str, font, pen.Brush, center.X - strWidth / 2.0f, center.Y + len);
}
}
}
using System.Globalization;
namespace DebugSample5
{
public static class Utils
{
public static int ParseIntInvariant(string s) => int.Parse(s, NumberFormatInfo.InvariantInfo);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment